usb_ac.c revision 77e515715b61e28fcf0c3f30936492888cecfd8b
2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A
2N/A/*
2N/A * AUDIO CONTROL Driver: usb_ac is a streams multiplexor that sits
2N/A * on top of usb_as and hid and is responsible for
2N/A * (1) providing the entry points to audio mixer framework, (2) passing
2N/A * streams messages to and from usb_as and hid and (3) processing
2N/A * control messages that it can handle.
2N/A *
2N/A * 1. Mixer entry points are: usb_ac_setup(), usb_ac_teardown(),
2N/A * usb_ac_set_config(), usb_ac_set_format(), usb_ac_start_play(),
2N/A * usb_ac_pause_play(), usb_ac_stop_play, usb_ac_start_record(),
2N/A * usb_ac_stop_record().
2N/A * 2. usb_ac is a streams driver that passes streams messages down to
2N/A * usb_as that selects the correct alternate with passed format
2N/A * parameters, sets sample frequency, starts play/record, stops
2N/A * play/record, pause play/record, open/close isoc pipe.
2N/A * 3. usb_ac handles the set_config command through the default pipe
2N/A * of sound control interface of the audio device in a synchronous
2N/A * manner.
2N/A *
2N/A * Serialization: usb_ac being a streams driver and having the requirement
2N/A * of making non-blockings calls (USBA or streams or mixer) needs to drop
2N/A * mutexes over such calls. But at the same time, a competing thread
2N/A * can't be allowed to interfere with (1) pipe, (2) streams state.
2N/A * So we need some kind of serialization among the asynchronous
2N/A * threads that can run in the driver. The serialization is mostly
2N/A * needed to avoid races among open/close/events/power entry points
2N/A * etc. Once a routine takes control, it checks if the resource (pipe or
2N/A * stream or dev state) is still accessible. If so, it proceeds with
2N/A * its job and until it completes, no other thread requiring the same
2N/A * resource can run.
2N/A *
2N/A * PM model in usb_ac: Raise power during attach. If a device is not at full
2N/A * power, raise power in the entry points. After the command is over,
2N/A * pm_idle_component() is called. The power is lowered in detach().
2N/A *
2N/A * locking: Warlock is not aware of the automatic locking mechanisms for
2N/A * streams drivers.
2N/A */
2N/A#include <sys/usb/usba/usbai_version.h>
2N/A#include <sys/usb/usba.h>
2N/A#include <sys/stropts.h>
2N/A#include <sys/sunndi.h>
2N/A#include <sys/ndi_impldefs.h>
2N/A#include <sys/strsubr.h>
2N/A
2N/A#include <sys/audio.h>
2N/A#include <sys/audiovar.h>
2N/A#include <sys/audio/audio_support.h>
2N/A#include <sys/audio/audio_src.h>
2N/A#include <sys/mixer.h>
2N/A#include <sys/audio/audio_mixer.h>
2N/A
2N/A#include <sys/usb/clients/audio/usb_audio.h>
2N/A#include <sys/usb/clients/audio/usb_mixer.h>
2N/A#include <sys/usb/clients/audio/usb_ac/usb_ac.h>
2N/A
2N/A/* debug support */
2N/Auint_t usb_ac_errlevel = USB_LOG_L4;
2N/Auint_t usb_ac_errmask = (uint_t)-1;
2N/Auint_t usb_ac_instance_debug = (uint_t)-1;
2N/A
2N/A#ifdef DEBUG
2N/A/*
2N/A * tunable timeout for usb_as response, allow at least 10 secs for control
2N/A * cmd to timeout
2N/A */
2N/Aint usb_ac_wait_timeout = 10000000;
2N/A#endif
2N/A
2N/A/*
2N/A * table for converting term types of input and output terminals
2N/A * to SADA port types (pretty rough mapping)
2N/A */
2N/Astatic struct {
2N/A ushort_t term_type;
2N/A ushort_t port_type;
2N/A} usb_ac_term_type_map[] = {
2N/A{ USB_AUDIO_TERM_TYPE_STREAMING, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_MICROPHONE, AUDIO_MICROPHONE },
2N/A{ USB_AUDIO_TERM_TYPE_DT_MICROPHONE, AUDIO_MICROPHONE },
2N/A{ USB_AUDIO_TERM_TYPE_PERS_MICROPHONE, AUDIO_MICROPHONE },
2N/A{ USB_AUDIO_TERM_TYPE_OMNI_DIR_MICROPHONE, AUDIO_MICROPHONE },
2N/A{ USB_AUDIO_TERM_TYPE_MICROPHONE_ARRAY, AUDIO_MICROPHONE },
2N/A{ USB_AUDIO_TERM_TYPE_PROCESSING_MIC_ARRAY, AUDIO_MICROPHONE },
2N/A{ USB_AUDIO_TERM_TYPE_SPEAKER, AUDIO_SPEAKER },
2N/A{ USB_AUDIO_TERM_TYPE_HEADPHONES, AUDIO_HEADPHONE },
2N/A{ USB_AUDIO_TERM_TYPE_DISPLAY_AUDIO, AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_DT_SPEAKER, AUDIO_SPEAKER },
2N/A{ USB_AUDIO_TERM_TYPE_ROOM_SPEAKER, AUDIO_SPEAKER },
2N/A{ USB_AUDIO_TERM_TYPE_COMM_SPEAKER, AUDIO_SPEAKER },
2N/A{ USB_AUDIO_TERM_TYPE_LF_EFFECTS_SPEAKER, AUDIO_SPEAKER },
2N/A{ USB_AUDIO_TERM_TYPE_HANDSET, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_HEADSET, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_SPEAKERPHONE, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_ECHO_SUPP_SPEAKERPHONE,
2N/A AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_ECHO_CANCEL_SPEAKERPHONE,
2N/A AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_PHONE_LINE, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_TELEPHONE, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_DOWN_LINE_PHONE, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_ANALOG_CONNECTOR, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_DIGITAL_AUDIO_IF, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_LINE_CONNECTOR, AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_LEGACY_AUDIO_CONNECTOR,
2N/A AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_SPDIF_IF, AUDIO_SPDIF_IN },
2N/A{ USB_AUDIO_TERM_TYPE_1394_DA_STREAM,
2N/A AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ USB_AUDIO_TERM_TYPE_1394_DV_STREAM_SNDTRCK,
2N/A AUDIO_LINE_IN|AUDIO_LINE_OUT },
2N/A{ 0, 0 }
2N/A};
2N/A
2N/A
2N/A/*
2N/A * Module linkage routines for the kernel
2N/A */
2N/Astatic int usb_ac_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
2N/Astatic int usb_ac_attach(dev_info_t *, ddi_attach_cmd_t);
2N/Astatic int usb_ac_detach(dev_info_t *, ddi_detach_cmd_t);
2N/Astatic int usb_ac_power(dev_info_t *, int, int);
2N/A
2N/A/*
2N/A * STREAMS module entry points
2N/A */
2N/Astatic int usb_ac_open(queue_t *, dev_t *, int, int, cred_t *);
2N/Astatic int usb_ac_close(queue_t *, int, cred_t *);
2N/Astatic int usb_ac_uwput(queue_t *, mblk_t *);
2N/Astatic int usb_ac_lrput(queue_t *, mblk_t *);
2N/A
2N/A/* plumbing */
2N/Astatic usb_ac_plumbed_t *usb_ac_get_plumb_info(usb_ac_state_t *, char *,
2N/A uchar_t);
2N/Astatic usb_ac_plumbed_t *usb_ac_get_plumb_info_from_lrq(usb_ac_state_t *,
2N/A queue_t *);
2N/Astatic uint_t usb_ac_get_featureID(usb_ac_state_t *, uchar_t, uint_t,
2N/A uint_t);
2N/Astatic void usb_ac_plumb_ioctl(queue_t *, mblk_t *);
2N/A
2N/A
2N/A/* registration */
2N/Astatic int usb_ac_get_curr_n_channels(usb_ac_state_t *, int);
2N/Astatic usb_audio_formats_t *usb_ac_get_curr_format(usb_ac_state_t *, int);
2N/A
2N/A/* descriptor handling */
2N/Astatic int usb_ac_handle_descriptors(usb_ac_state_t *);
2N/Astatic void usb_ac_add_unit_descriptor(usb_ac_state_t *, uchar_t *, size_t);
2N/Astatic void usb_ac_alloc_unit(usb_ac_state_t *, uint_t);
2N/Astatic void usb_ac_free_all_units(usb_ac_state_t *);
2N/Astatic void usb_ac_setup_connections(usb_ac_state_t *);
2N/Astatic void usb_ac_map_termtype_to_port(usb_ac_state_t *, uint_t);
2N/A
2N/A/* power management */
2N/Astatic int usb_ac_pwrlvl0(usb_ac_state_t *);
2N/Astatic int usb_ac_pwrlvl1(usb_ac_state_t *);
2N/Astatic int usb_ac_pwrlvl2(usb_ac_state_t *);
2N/Astatic int usb_ac_pwrlvl3(usb_ac_state_t *);
2N/Astatic void usb_ac_create_pm_components(dev_info_t *, usb_ac_state_t *);
2N/Astatic void usb_ac_pm_busy_component(usb_ac_state_t *);
2N/Astatic void usb_ac_pm_idle_component(usb_ac_state_t *);
2N/A
2N/A/* event handling */
2N/Astatic int usb_ac_disconnect_event_cb(dev_info_t *);
2N/Astatic int usb_ac_reconnect_event_cb(dev_info_t *);
2N/Astatic int usb_ac_cpr_suspend(dev_info_t *);
2N/Astatic void usb_ac_cpr_resume(dev_info_t *);
2N/A
2N/Astatic usb_event_t usb_ac_events = {
2N/A usb_ac_disconnect_event_cb,
2N/A usb_ac_reconnect_event_cb,
2N/A NULL, NULL
2N/A};
2N/A
2N/A/* misc. support */
2N/Astatic void usb_ac_restore_device_state(dev_info_t *, usb_ac_state_t *);
2N/Astatic int usb_ac_cleanup(dev_info_t *, usb_ac_state_t *);
2N/Astatic void usb_ac_serialize_access(usb_ac_state_t *);
2N/Astatic void usb_ac_release_access(usb_ac_state_t *);
2N/A
2N/Astatic void usb_ac_push_unit_id(usb_ac_state_t *, uint_t);
2N/Astatic void usb_ac_pop_unit_id(usb_ac_state_t *, uint_t);
2N/Astatic void usb_ac_show_traverse_path(usb_ac_state_t *);
2N/Astatic int usb_ac_check_path(usb_ac_state_t *, uint_t);
2N/A
2N/Astatic uint_t usb_ac_traverse_connections(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t,
2N/A uint_t *, uint_t, uint_t *,
2N/A int (*func)(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t *));
2N/Astatic uint_t usb_ac_set_port(usb_ac_state_t *, uint_t, uint_t);
2N/Astatic uint_t usb_ac_set_control(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t,
2N/A uint_t *, uint_t,
2N/A int (*func)(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t *));
2N/Astatic uint_t usb_ac_set_monitor_gain_control(usb_ac_state_t *, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t,
2N/A uint_t *, uint_t,
2N/A int (*func)(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t *));
2N/Astatic uint_t usb_ac_traverse_all_units(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t *,
2N/A uint_t, uint_t *,
2N/A int (*func)(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t *));
2N/Astatic int usb_ac_update_port(usb_ac_state_t *, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t, uint_t *);
2N/Astatic int usb_ac_set_selector(usb_ac_state_t *, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t, uint_t *);
2N/Astatic int usb_ac_feature_unit_check(usb_ac_state_t *, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t, uint_t *);
2N/Astatic int usb_ac_set_gain(usb_ac_state_t *, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t, uint_t *);
2N/Astatic int usb_ac_set_monitor_gain(usb_ac_state_t *, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t, uint_t *);
2N/Astatic int usb_ac_set_mute(usb_ac_state_t *, uint_t, uint_t,
2N/A uint_t, uint_t, uint_t, uint_t *);
2N/Astatic int usb_ac_set_volume(usb_ac_state_t *, uint_t, short, int dir,
2N/A int);
2N/Astatic int usb_ac_get_maxmin_volume(usb_ac_state_t *, uint_t, int, int,
2N/A int);
2N/Astatic void usb_ac_free_mblk(mblk_t *);
2N/Astatic mblk_t *usb_ac_allocate_req_mblk(usb_ac_state_t *, int,
2N/A void *, uint_t);
2N/Astatic int usb_ac_send_as_cmd(usb_ac_state_t *, usb_ac_plumbed_t *,
2N/A int, void *);
2N/Astatic int usb_ac_send_format_cmd(audiohdl_t, int, int, int,
2N/A int, int, int);
2N/Astatic int usb_ac_do_setup(audiohdl_t, int, int);
2N/Astatic void usb_ac_do_teardown(audiohdl_t, int, int);
2N/Astatic void usb_ac_do_pause_play(audiohdl_t, int);
2N/Astatic void usb_ac_do_stop_record(audiohdl_t, int);
2N/A
2N/A/* Mixer entry points */
2N/Astatic int usb_ac_setup(audiohdl_t, int, int);
2N/Astatic void usb_ac_teardown(audiohdl_t, int, int);
2N/Astatic int usb_ac_set_config(audiohdl_t, int, int, int, int, int);
2N/Astatic int usb_ac_set_format(audiohdl_t, int, int, int, int, int, int);
2N/Astatic int usb_ac_start_play(audiohdl_t, int);
2N/Astatic void usb_ac_pause_play(audiohdl_t, int);
2N/Astatic void usb_ac_stop_play(audiohdl_t, int);
2N/Astatic int usb_ac_start_record(audiohdl_t, int);
2N/Astatic void usb_ac_stop_record(audiohdl_t, int);
2N/Astatic int usb_ac_restore_audio_state(usb_ac_state_t *, int);
2N/A
2N/A/*
2N/A * External functions
2N/A */
2N/Aextern int space_store(char *key, uintptr_t ptr);
2N/Aextern void space_free(char *);
2N/A
2N/A
2N/A/*
2N/A * mixer registration data
2N/A */
2N/Astatic am_ad_entry_t usb_ac_entry = {
2N/A usb_ac_setup, /* ad_setup() */
2N/A usb_ac_teardown, /* ad_teardown() */
2N/A usb_ac_set_config, /* ad_set_config() */
2N/A usb_ac_set_format, /* ad_set_format() */
2N/A usb_ac_start_play, /* ad_start_play() */
2N/A usb_ac_pause_play, /* ad_pause_play() */
2N/A usb_ac_stop_play, /* ad_stop_play() */
2N/A usb_ac_start_record, /* ad_start_record() */
2N/A usb_ac_stop_record, /* ad_stop_record() */
2N/A NULL, /* ad_ioctl() */
2N/A NULL /* ad_iocdata() */
2N/A};
2N/A
2N/A/* anchor for soft state structures */
2N/Astatic void *usb_ac_statep;
2N/A
2N/A/* for passing soft state etc. to usb_ac_dacf module */
2N/Astatic usb_ac_state_space_t ssp;
2N/A
2N/A/* STREAMS driver id and limit value structure */
2N/Astatic struct module_info usb_ac_modinfo = {
2N/A 0xffff, /* module ID number */
2N/A "usb_ac", /* module name */
2N/A USB_AUDIO_MIN_PKTSZ, /* minimum packet size */
2N/A USB_AUDIO_MAX_PKTSZ, /* maximum packet size */
2N/A USB_AC_HIWATER, /* high water mark */
2N/A USB_AC_LOWATER /* low water mark */
2N/A};
2N/A
2N/A/* STREAMS queue processing procedures structures */
2N/A/* upper read queue */
2N/Astatic struct qinit usb_ac_urqueue = {
2N/A NULL, /* put procedure */
2N/A NULL, /* service procedure */
2N/A usb_ac_open, /* open procedure */
2N/A usb_ac_close, /* close procedure */
2N/A NULL, /* unused */
2N/A &usb_ac_modinfo, /* module parameters */
2N/A NULL /* module statistics */
2N/A};
2N/A
2N/A/* upper write queue */
2N/Astatic struct qinit usb_ac_uwqueue = {
2N/A usb_ac_uwput, /* put procedure */
2N/A audio_sup_wsvc, /* service procedure */
2N/A NULL, /* open procedure */
2N/A NULL, /* close procedure */
2N/A NULL, /* unused */
2N/A &usb_ac_modinfo, /* module parameters */
2N/A NULL /* module statistics */
2N/A};
2N/A
2N/A/* lower read queue */
2N/Astatic struct qinit usb_ac_lrqueue = {
2N/A usb_ac_lrput,
2N/A NULL,
2N/A NULL,
2N/A NULL,
2N/A NULL,
2N/A &usb_ac_modinfo, /* module parameters */
2N/A NULL
2N/A};
2N/A
2N/A/* lower write queue */
2N/Astatic struct qinit usb_ac_lwqueue = {
2N/A NULL,
2N/A NULL,
2N/A NULL,
2N/A NULL,
2N/A NULL,
2N/A &usb_ac_modinfo, /* module parameters */
2N/A NULL
2N/A};
2N/A
2N/A/* STREAMS entity declaration structure */
2N/Astatic struct streamtab usb_ac_str_info = {
2N/A &usb_ac_urqueue, /* upper read queue */
2N/A &usb_ac_uwqueue, /* upper write queue */
2N/A &usb_ac_lrqueue, /* lower read queue */
2N/A &usb_ac_lwqueue, /* lower write queue */
2N/A};
2N/A
2N/A/*
2N/A * DDI Structures
2N/A *
2N/A * Entry points structure
2N/A */
2N/Astatic struct cb_ops usb_ac_cb_ops = {
2N/A nulldev, /* cb_open */
2N/A nulldev, /* cb_close */
2N/A nodev, /* cb_strategy */
2N/A nodev, /* cb_print */
2N/A nodev, /* cb_dump */
2N/A nodev, /* cb_read */
2N/A nodev, /* cb_write */
2N/A nodev, /* cb_ioctl */
2N/A nodev, /* cb_devmap */
2N/A nodev, /* cb_mmap */
2N/A nodev, /* cb_segmap */
2N/A nochpoll, /* cb_chpoll */
2N/A ddi_prop_op, /* cb_prop_op */
2N/A &usb_ac_str_info, /* cb_str */
2N/A D_MP | D_MTPERQ, /* cb_flag */
2N/A CB_REV, /* cb_rev */
2N/A nodev, /* cb_aread */
2N/A nodev, /* cb_arwite */
2N/A};
2N/A
2N/A/* Device operations structure */
2N/Astatic struct dev_ops usb_ac_dev_ops = {
2N/A DEVO_REV, /* devo_rev */
2N/A 0, /* devo_refcnt */
2N/A usb_ac_getinfo, /* devo_getinfo */
2N/A nulldev, /* devo_identify - obsolete */
2N/A nulldev, /* devo_probe - not needed */
2N/A usb_ac_attach, /* devo_attach */
2N/A usb_ac_detach, /* devo_detach */
2N/A nodev, /* devo_reset */
2N/A &usb_ac_cb_ops, /* devi_cb_ops */
2N/A NULL, /* devo_busb_ac_ops */
2N/A usb_ac_power /* devo_power */
2N/A};
2N/A
2N/A/* Linkage structure for loadable drivers */
2N/Astatic struct modldrv usb_ac_modldrv = {
2N/A &mod_driverops, /* drv_modops */
2N/A "USB Audio Control Driver", /* drv_linkinfo */
2N/A &usb_ac_dev_ops /* drv_dev_ops */
2N/A};
2N/A
2N/A/* Module linkage structure */
2N/Astatic struct modlinkage usb_ac_modlinkage = {
2N/A MODREV_1, /* ml_rev */
2N/A (void *)&usb_ac_modldrv, /* ml_linkage */
2N/A NULL /* NULL terminates the list */
2N/A};
2N/A
2N/A/* warlock directives */
2N/A_NOTE(SCHEME_PROTECTS_DATA("unique per call", iocblk))
2N/A_NOTE(SCHEME_PROTECTS_DATA("unique per call", datab))
2N/A_NOTE(SCHEME_PROTECTS_DATA("unique per call", msgb))
2N/A_NOTE(SCHEME_PROTECTS_DATA("unique per call", queue))
2N/A_NOTE(SCHEME_PROTECTS_DATA("stable data", usb_pipe_policy_t))
2N/A
2N/A/* standard entry points */
2N/Aint
2N/A_init(void)
2N/A{
2N/A int rval;
2N/A
2N/A /* initialize the soft state */
2N/A if ((rval = ddi_soft_state_init(&usb_ac_statep,
2N/A sizeof (usb_ac_state_t), 1)) != DDI_SUCCESS) {
2N/A return (rval);
2N/A }
2N/A
2N/A if ((rval = mod_install(&usb_ac_modlinkage)) != 0) {
2N/A ddi_soft_state_fini(&usb_ac_statep);
2N/A }
2N/A
2N/A if (!rval) {
2N/A ssp.sp = usb_ac_statep;
2N/A ssp.restore_func = usb_ac_restore_audio_state;
2N/A ssp.get_featureID_func = usb_ac_get_featureID;
2N/A ssp.ac_entryp = &usb_ac_entry;
2N/A ssp.pm_busy_component = usb_ac_pm_busy_component;
2N/A ssp.pm_idle_component = usb_ac_pm_idle_component;
2N/A
2N/A rval = space_store("usb_ac", (uintptr_t)&ssp);
2N/A }
2N/A
2N/A return (rval);
2N/A}
2N/A
2N/A
2N/Aint
2N/A_fini(void)
2N/A{
2N/A int rval;
2N/A
2N/A if ((rval = mod_remove(&usb_ac_modlinkage)) == 0) {
2N/A /* Free the soft state internal structures */
2N/A ddi_soft_state_fini(&usb_ac_statep);
2N/A space_free("usb_ac");
2N/A }
2N/A
2N/A return (rval);
2N/A}
2N/A
2N/A
2N/Aint
2N/A_info(struct modinfo *modinfop)
2N/A{
2N/A return (mod_info(&usb_ac_modlinkage, modinfop));
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Astatic int
2N/Ausb_ac_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
2N/A void *arg, void **result)
2N/A{
2N/A usb_ac_state_t *uacp = NULL;
2N/A int error = DDI_FAILURE;
2N/A int instance;
2N/A
2N/A switch (infocmd) {
2N/A case DDI_INFO_DEVT2DEVINFO:
2N/A instance = audio_sup_devt_to_instance((dev_t)arg);
2N/A if ((uacp = ddi_get_soft_state(usb_ac_statep,
2N/A instance)) != NULL) {
2N/A *result = uacp->usb_ac_dip;
2N/A if (*result != NULL) {
2N/A error = DDI_SUCCESS;
2N/A }
2N/A } else {
2N/A *result = NULL;
2N/A }
2N/A break;
2N/A case DDI_INFO_DEVT2INSTANCE:
2N/A *result = (void *)(uintptr_t)
2N/A audio_sup_devt_to_instance((dev_t)arg);
2N/A error = DDI_SUCCESS;
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A
2N/A return (error);
2N/A}
2N/A
2N/Aextern uint_t nproc;
2N/A#define INIT_PROCESS_CNT 3
2N/A
2N/Astatic int
2N/Ausb_ac_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
2N/A{
2N/A usb_ac_state_t *uacp = NULL;
2N/A audio_sup_reg_data_t reg_data;
2N/A int instance = ddi_get_instance(dip);
2N/A int minor;
2N/A char *key;
2N/A size_t key_len, len;
2N/A
2N/A switch (cmd) {
2N/A case DDI_ATTACH:
2N/A break;
2N/A case DDI_RESUME:
2N/A usb_ac_cpr_resume(dip);
2N/A
2N/A return (DDI_SUCCESS);
2N/A default:
2N/A return (DDI_FAILURE);
2N/A }
2N/A
2N/A /*
2N/A * wait until all processes are started from main.
2N/A * USB enumerates early in boot (ie. consconfig time).
2N/A * If the plumbing takes place early, the file descriptors
2N/A * are owned by the init process and can never be closed anymore
2N/A * Consequently, hot removal is not possible and the dips
2N/A * never go away. By waiting some time, e.g. INIT_PROCESS_CNT,
2N/A * the problem is avoided.
2N/A */
2N/A if (nproc < INIT_PROCESS_CNT) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL,
2N/A "usb_ac%d attach too early", instance);
2N/A
2N/A return (DDI_FAILURE);
2N/A }
2N/A
2N/A /*
2N/A * Allocate soft state information.
2N/A */
2N/A if (ddi_soft_state_zalloc(usb_ac_statep, instance) != DDI_SUCCESS) {
2N/A
2N/A goto fail;
2N/A }
2N/A
2N/A /*
2N/A * get soft state space and initialize
2N/A */
2N/A uacp = (usb_ac_state_t *)ddi_get_soft_state(usb_ac_statep, instance);
2N/A if (uacp == NULL) {
2N/A
2N/A goto fail;
2N/A }
2N/A
2N/A
2N/A /* get log handle */
2N/A uacp->usb_ac_log_handle = usb_alloc_log_hdl(dip, "ac",
2N/A &usb_ac_errlevel,
2N/A &usb_ac_errmask, &usb_ac_instance_debug,
2N/A 0);
2N/A
2N/A uacp->usb_ac_instance = instance;
2N/A uacp->usb_ac_dip = dip;
2N/A
2N/A if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "usb_client_attach failed");
2N/A
2N/A usb_free_log_hdl(uacp->usb_ac_log_handle);
2N/A ddi_soft_state_free(usb_ac_statep, uacp->usb_ac_instance);
2N/A
2N/A return (DDI_FAILURE);
2N/A }
2N/A
2N/A if (usb_get_dev_data(dip, &uacp->usb_ac_dev_data,
2N/A USB_PARSE_LVL_IF, 0) != USB_SUCCESS) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "usb_get_dev_data failed");
2N/A
2N/A usb_client_detach(dip, NULL);
2N/A usb_free_log_hdl(uacp->usb_ac_log_handle);
2N/A ddi_soft_state_free(usb_ac_statep, uacp->usb_ac_instance);
2N/A
2N/A return (DDI_FAILURE);
2N/A }
2N/A
2N/A /* initialize mutex & cv */
2N/A mutex_init(&uacp->usb_ac_mutex, NULL, MUTEX_DRIVER,
2N/A uacp->usb_ac_dev_data->dev_iblock_cookie);
2N/A
2N/A uacp->usb_ac_default_ph = uacp->usb_ac_dev_data->dev_default_ph;
2N/A
2N/A /* register with audiosup */
2N/A reg_data.asrd_version = AUDIOSUP_VERSION;
2N/A
2N/A /*
2N/A * we register with pathname, the mgf, product, and serial number
2N/A * strings, vid.pid, and driver name which should be pretty unique
2N/A */
2N/A key_len = 2 * MAXNAMELEN;
2N/A if (uacp->usb_ac_dev_data->dev_mfg) {
2N/A key_len += strlen(uacp->usb_ac_dev_data->dev_mfg);
2N/A }
2N/A if (uacp->usb_ac_dev_data->dev_product) {
2N/A key_len += strlen(uacp->usb_ac_dev_data->dev_product);
2N/A }
2N/A if (uacp->usb_ac_dev_data->dev_serial) {
2N/A key_len += strlen(uacp->usb_ac_dev_data->dev_serial);
2N/A }
2N/A
2N/A key = kmem_alloc(key_len, KM_SLEEP);
2N/A (void) ddi_pathname(dip, key);
2N/A
2N/A len = strlen(key);
2N/A (void) snprintf(&key[len], key_len - len, ",%s,%s,%s,%x.%x,%s",
2N/A (uacp->usb_ac_dev_data->dev_mfg ?
2N/A uacp->usb_ac_dev_data->dev_mfg : "-"),
2N/A (uacp->usb_ac_dev_data->dev_product ?
2N/A uacp->usb_ac_dev_data->dev_product : "-"),
2N/A (uacp->usb_ac_dev_data->dev_serial ?
2N/A uacp->usb_ac_dev_data->dev_serial : "-"),
2N/A uacp->usb_ac_dev_data->dev_descr->idVendor,
2N/A uacp->usb_ac_dev_data->dev_descr->idProduct,
2N/A ddi_driver_name(dip));
2N/A
2N/A reg_data.asrd_key = key;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "registering with key: %s", key);
2N/A
2N/A uacp->usb_ac_audiohdl = audio_sup_register(dip, &reg_data);
2N/A kmem_free(key, key_len);
2N/A
2N/A if (uacp->usb_ac_audiohdl == NULL) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "audio_sup_register failed");
2N/A
2N/A goto fail;
2N/A }
2N/A
2N/A /* save softstate pointer in audio handle */
2N/A audio_sup_set_private(uacp->usb_ac_audiohdl, (void *)uacp);
2N/A
2N/A /* parse all class specific descriptors */
2N/A if (usb_ac_handle_descriptors(uacp) != USB_SUCCESS) {
2N/A
2N/A goto fail;
2N/A }
2N/A
2N/A /* we no longer need the descr tree */
2N/A usb_free_descr_tree(dip, uacp->usb_ac_dev_data);
2N/A
2N/A /* read .conf file properties */
2N/A uacp->usb_ac_mixer_mode_enable = ddi_prop_get_int(DDI_DEV_T_ANY,
2N/A dip, DDI_PROP_DONTPASS, "mixer-enabled", 1);
2N/A
2N/A uacp->usb_ac_ser_acc = usb_init_serialization(dip,
2N/A USB_INIT_SER_CHECK_SAME_THREAD);
2N/A
2N/A /* create minor node */
2N/A minor = audio_sup_construct_minor(uacp->usb_ac_audiohdl, USER1);
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "minor=%d", minor);
2N/A
2N/A if ((ddi_create_minor_node(dip, "mux", S_IFCHR,
2N/A minor, NULL, 0)) != DDI_SUCCESS) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "usb_ac_attach: couldn't create minor node mux");
2N/A
2N/A goto fail;
2N/A }
2N/A uacp->usb_ac_mux_minor = minor;
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A
2N/A /* we are online */
2N/A uacp->usb_ac_dev_state = USB_DEV_ONLINE;
2N/A
2N/A /*
2N/A * safe guard the postattach to be executed
2N/A * only two states arepossible: plumbed / unplumbed
2N/A */
2N/A uacp->usb_ac_plumbing_state = USB_AC_STATE_UNPLUMBED;
2N/A uacp->usb_ac_current_plumbed_index = -1;
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A /* create components to power manage this device */
2N/A usb_ac_create_pm_components(dip, uacp);
2N/A
2N/A /* Register for events */
2N/A if (usb_register_event_cbs(dip, &usb_ac_events, 0) != USB_SUCCESS) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "usb_ac_attach: couldn't register for events");
2N/A
2N/A goto fail;
2N/A }
2N/A
2N/A /* report device */
2N/A ddi_report_dev(dip);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "usb_ac_attach: End");
2N/A
2N/A return (DDI_SUCCESS);
2N/Afail:
2N/A if (uacp) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "attach failed");
2N/A (void) usb_ac_cleanup(dip, uacp);
2N/A }
2N/A
2N/A return (DDI_FAILURE);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Ausb_ac_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
2N/A{
2N/A int instance = ddi_get_instance(dip);
2N/A usb_ac_state_t *uacp;
2N/A int rval;
2N/A
2N/A uacp = ddi_get_soft_state(usb_ac_statep, instance);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_detach:");
2N/A
2N/A switch (cmd) {
2N/A case DDI_DETACH:
2N/A rval = usb_ac_cleanup(dip, uacp);
2N/A
2N/A return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE);
2N/A case DDI_SUSPEND:
2N/A rval = usb_ac_cpr_suspend(dip);
2N/A
2N/A return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE);
2N/A default:
2N/A
2N/A return (DDI_FAILURE);
2N/A }
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_cleanup:
2N/A * cleanup on attach failure and detach
2N/A */
2N/Astatic int
2N/Ausb_ac_cleanup(dev_info_t *dip, usb_ac_state_t *uacp)
2N/A{
2N/A usb_ac_power_t *uacpm;
2N/A int rval = USB_FAILURE;
2N/A
2N/A ASSERT(uacp);
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A uacpm = uacp->usb_ac_pm;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_cleanup: uacpm=0x%p", (void *)uacpm);
2N/A
2N/A ASSERT(uacp->usb_ac_busy_count == 0);
2N/A
2N/A ASSERT(uacp->usb_ac_plumbing_state == USB_AC_STATE_UNPLUMBED);
2N/A
2N/A /*
2N/A * deregister with audio framework, if it fails we are hosed
2N/A * and we probably don't want to plumb again
2N/A */
2N/A if (uacp->usb_ac_audiohdl) {
2N/A if (uacp->usb_ac_registered_with_mixer) {
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A if (am_detach(uacp->usb_ac_audiohdl, DDI_DETACH) !=
2N/A AUDIO_SUCCESS) {
2N/A
2N/A return (rval);
2N/A }
2N/A } else {
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A }
2N/A if (audio_sup_unregister(uacp->usb_ac_audiohdl) !=
2N/A AUDIO_SUCCESS) {
2N/A
2N/A return (rval);
2N/A }
2N/A } else {
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A }
2N/A
2N/A /*
2N/A * Disable the event callbacks, after this point, event
2N/A * callbacks will never get called. Note we shouldn't hold
2N/A * the mutex while unregistering events because there may be a
2N/A * competing event callback thread. Event callbacks are done
2N/A * with ndi mutex held and this can cause a potential deadlock.
2N/A */
2N/A usb_unregister_event_cbs(dip, &usb_ac_events);
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A
2N/A if (uacpm && (uacp->usb_ac_dev_state != USB_DEV_DISCONNECTED)) {
2N/A if (uacpm->acpm_wakeup_enabled) {
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A usb_ac_pm_busy_component(uacp);
2N/A (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
2N/A
2N/A rval = usb_handle_remote_wakeup(dip,
2N/A USB_REMOTE_WAKEUP_DISABLE);
2N/A if (rval != USB_SUCCESS) {
2N/A USB_DPRINTF_L2(PRINT_MASK_PM,
2N/A uacp->usb_ac_log_handle,
2N/A "usb_ac_cleanup: disable remote "
2N/A "wakeup failed, rval=%d", rval);
2N/A }
2N/A usb_ac_pm_idle_component(uacp);
2N/A } else {
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A }
2N/A
2N/A (void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF);
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A }
2N/A
2N/A if (uacpm) {
2N/A kmem_free(uacpm, sizeof (usb_ac_power_t));
2N/A uacp->usb_ac_pm = NULL;
2N/A }
2N/A
2N/A usb_client_detach(dip, uacp->usb_ac_dev_data);
2N/A
2N/A /* free descriptors */
2N/A usb_ac_free_all_units(uacp);
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A mutex_destroy(&uacp->usb_ac_mutex);
2N/A
2N/A usb_fini_serialization(uacp->usb_ac_ser_acc);
2N/A
2N/A ddi_remove_minor_node(dip, NULL);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_cleanup: Ending");
2N/A
2N/A usb_free_log_hdl(uacp->usb_ac_log_handle);
2N/A kmem_free(uacp->usb_ac_connections, uacp->usb_ac_connections_len);
2N/A kmem_free(uacp->usb_ac_connections_a, uacp->usb_ac_connections_a_len);
2N/A kmem_free(uacp->usb_ac_unit_type, uacp->usb_ac_max_unit);
2N/A kmem_free(uacp->usb_ac_traverse_path, uacp->usb_ac_max_unit);
2N/A
2N/A ddi_soft_state_free(usb_ac_statep, uacp->usb_ac_instance);
2N/A
2N/A ddi_prop_remove_all(dip);
2N/A
2N/A return (USB_SUCCESS);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_open:
2N/A * Open entry point. Called on the plumbing minor node or
2N/A * audio or audioctl minor nodes which we pass to audio_sup_open()
2N/A * We do not raise power here and wait for the setup callback
2N/A */
2N/A/*ARGSUSED*/
2N/Astatic int
2N/Ausb_ac_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *credp)
2N/A{
2N/A int minor = getminor(*devp);
2N/A int instance;
2N/A int rval;
2N/A usb_ac_state_t *uacp;
2N/A
2N/A instance = audio_sup_devt_to_instance(*devp);
2N/A
2N/A uacp = ddi_get_soft_state(usb_ac_statep, instance);
2N/A if (uacp == NULL) {
2N/A
2N/A return (ENXIO);
2N/A }
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A uacp->usb_ac_busy_count++; /* This will prevent unplumbing */
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_OPEN, uacp->usb_ac_log_handle,
2N/A "usb_ac_open: Begin q=0x%p, minor=0x%x instance=%d "
2N/A "open cnt=%d", (void *)q, minor, instance, uacp->usb_ac_busy_count);
2N/A
2N/A if (sflag) {
2N/A USB_DPRINTF_L2(PRINT_MASK_OPEN, uacp->usb_ac_log_handle,
2N/A "usb_ac_open: clone open not supported");
2N/A
2N/A uacp->usb_ac_busy_count--;
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A return (ENXIO);
2N/A }
2N/A
2N/A if (minor == uacp->usb_ac_mux_minor) {
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_OPEN, uacp->usb_ac_log_handle,
2N/A "usb_ac_open: opening mux");
2N/A /*
2N/A * This is the plumbing open, initiated during attach/
2N/A * connect_event_callback/cpr_resume/first user open.
2N/A */
2N/A uacp->usb_ac_busy_count--;
2N/A
2N/A /* Save the dev_t value of pluming q to use for lower q's */
2N/A uacp->usb_ac_dev = *devp;
2N/A audio_sup_set_qptr(q, *devp, (void *)uacp);
2N/A
2N/A /* Initialize the queue pointers */
2N/A uacp->usb_ac_rq = q;
2N/A uacp->usb_ac_wq = WR(q);
2N/A
2N/A /* release mutex while making streams framework call */
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A qprocson(q);
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A
2N/A } else if (uacp->usb_ac_plumbing_state != USB_AC_STATE_PLUMBED) {
2N/A uacp->usb_ac_busy_count--;
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A return (EIO);
2N/A } else {
2N/A /* pass the open to audio_sup_open so SADA can do its work */
2N/A USB_DPRINTF_L4(PRINT_MASK_OPEN, uacp->usb_ac_log_handle,
2N/A "usb_ac_open: calling audio_sup_open, q=0x%p, open_cnt=%d",
2N/A (void *)q, uacp->usb_ac_busy_count);
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A /*
2N/A * go to full power
2N/A */
2N/A usb_ac_pm_busy_component(uacp);
2N/A (void) pm_raise_power(uacp->usb_ac_dip, 0, USB_DEV_OS_FULL_PWR);
2N/A
2N/A rval = audio_sup_open(q, devp, flag, sflag, credp);
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A
2N/A if (rval != 0) {
2N/A USB_DPRINTF_L4(PRINT_MASK_OPEN,
2N/A uacp->usb_ac_log_handle,
2N/A "audio_sup_open rval=%d", rval);
2N/A
2N/A uacp->usb_ac_busy_count--;
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A usb_ac_pm_idle_component(uacp);
2N/A
2N/A return (rval);
2N/A }
2N/A }
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_OPEN, uacp->usb_ac_log_handle,
2N/A "usb_ac_open: End q=0x%p, open cnt=%d",
2N/A (void *)q, uacp->usb_ac_busy_count);
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_close :
2N/A * Close entry point
2N/A */
2N/A/*ARGSUSED*/
2N/Astatic int
2N/Ausb_ac_close(queue_t *q, int flag, cred_t *credp)
2N/A{
2N/A dev_t dev = audio_sup_get_qptr_dev(q);
2N/A int minor = getminor(dev);
2N/A int instance = audio_sup_get_qptr_instance(q);
2N/A usb_ac_state_t *uacp = ddi_get_soft_state(usb_ac_statep, instance);
2N/A int rval;
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_CLOSE, uacp->usb_ac_log_handle,
2N/A "usb_ac_close: Begin q=0x%p, opencount=%d",
2N/A (void *)q, uacp->usb_ac_busy_count);
2N/A
2N/A /* closing the mux? */
2N/A if (minor == uacp->usb_ac_mux_minor) {
2N/A USB_DPRINTF_L4(PRINT_MASK_CLOSE, uacp->usb_ac_log_handle,
2N/A "usb_ac_close: closing mux plumbing stream");
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A /* Wait till all activity in the default pipe has drained */
2N/A usb_ac_serialize_access(uacp);
2N/A usb_ac_release_access(uacp);
2N/A
2N/A audio_sup_free_qptr(q);
2N/A qprocsoff(q);
2N/A
2N/A return (0);
2N/A }
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A rval = audio_sup_close(q, flag, credp);
2N/A
2N/A if (rval != 0) {
2N/A USB_DPRINTF_L2(PRINT_MASK_CLOSE, uacp->usb_ac_log_handle,
2N/A "audio_sup_close fails %d", rval);
2N/A
2N/A return (rval);
2N/A }
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A
2N/A /* normal streams closing */
2N/A ASSERT(uacp->usb_ac_plumbing_state >= USB_AC_STATE_PLUMBED);
2N/A
2N/A uacp->usb_ac_busy_count --;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_CLOSE, uacp->usb_ac_log_handle,
2N/A "usb_ac_close: End rval=%d q=0x%p, opencount=%d",
2N/A rval, (void *)q, uacp->usb_ac_busy_count);
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A usb_ac_pm_idle_component(uacp);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_uwput:
2N/A * write put entry point for the upper mux. Only PLUMB/UNPLUMB ioctls
2N/A * are processed here. All other ioctls are passed to audio_sup routines
2N/A * for further processing.
2N/A */
2N/Astatic int
2N/Ausb_ac_uwput(queue_t *q, mblk_t *mp)
2N/A{
2N/A int instance = audio_sup_get_qptr_instance(q);
2N/A usb_ac_state_t *uacp = ddi_get_soft_state(usb_ac_statep, instance);
2N/A int error = DDI_SUCCESS;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_uwput: q=0x%p, mp=0x%p", (void *)q, (void *)mp);
2N/A
2N/A ASSERT(mp != NULL);
2N/A ASSERT(mp->b_datap != NULL);
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A if (uacp->usb_ac_wq == q) {
2N/A ASSERT(mp->b_datap->db_type == M_IOCTL);
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A /* ioctl from plumbing thread (namely P_LINK) */
2N/A usb_ac_plumb_ioctl(q, mp);
2N/A
2N/A return (error);
2N/A }
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A /* Pass to audio_sup routine */
2N/A (void) audio_sup_wput(q, mp);
2N/A
2N/A return (error);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_lrput:
2N/A * read put entry point for the lower mux. Get the response from the
2N/A * lower module, signal usb_ac_send_as_cmd(), the thread that is waiting
2N/A * for a response to a message sent earlier anbd pass the response
2N/A * message block.
2N/A */
2N/Astatic int
2N/Ausb_ac_lrput(queue_t *q, mblk_t *mp)
2N/A{
2N/A int instance = audio_sup_get_qptr_instance(q);
2N/A usb_ac_state_t *uacp;
2N/A int error = DDI_SUCCESS;
2N/A usb_ac_plumbed_t *plumb_infop;
2N/A usb_ac_streams_info_t *streams_infop = NULL;
2N/A int val;
2N/A char val1;
2N/A struct iocblk *iocp;
2N/A
2N/A uacp = ddi_get_soft_state(usb_ac_statep, instance);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_lrput: q=0x%p, mp=0x%p, instance=%d",
2N/A (void *)q, (void *)mp, instance);
2N/A ASSERT(mp != NULL);
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A plumb_infop = usb_ac_get_plumb_info_from_lrq(uacp, q);
2N/A ASSERT(plumb_infop != NULL);
2N/A
2N/A switch (mp->b_datap->db_type) {
2N/A case M_CTL:
2N/A case M_ERROR:
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "M_CTL/M_ERROR");
2N/A
2N/A switch (plumb_infop->acp_driver) {
2N/A case USB_AS_PLUMBED:
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "reply from usb_as, lrq=0x%p", (void *)q);
2N/A streams_infop = (usb_ac_streams_info_t *)
2N/A plumb_infop->acp_data;
2N/A ASSERT(streams_infop != NULL);
2N/A streams_infop->acs_ac_to_as_req.acr_reply_mp = mp;
2N/A streams_infop->acs_ac_to_as_req.acr_wait_flag = 0;
2N/A cv_signal(&streams_infop->acs_ac_to_as_req.acr_cv);
2N/A
2N/A break;
2N/A case USB_AH_PLUMBED:
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "M_CTL from hid, lrq=0x%p", (void *)q);
2N/A
2N/A iocp = (struct iocblk *)mp->b_rptr;
2N/A ASSERT(mp->b_cont != NULL);
2N/A
2N/A if (uacp->usb_ac_registered_with_mixer) {
2N/A
2N/A val1 = *((char *)mp->b_cont->b_rptr);
2N/A val = (int)val1;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL,
2N/A uacp->usb_ac_log_handle, "val1=0x%x(%d),"
2N/A "val=0x%x(%d)", val1, val1, val, val);
2N/A
2N/A switch (iocp->ioc_cmd) {
2N/A /* Handle relative volume change */
2N/A case USB_AUDIO_VOL_CHANGE:
2N/A /* prevent unplumbing */
2N/A uacp->usb_ac_busy_count++;
2N/A if (uacp->usb_ac_plumbing_state ==
2N/A USB_AC_STATE_PLUMBED) {
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A (void) am_hw_state_change(
2N/A uacp->usb_ac_audiohdl,
2N/A AM_HWSC_SET_GAIN_DELTA,
2N/A AUDIO_PLAY, val,
2N/A AUDIO_NO_SLEEP);
2N/A mutex_enter(&uacp->
2N/A usb_ac_mutex);
2N/A }
2N/A uacp->usb_ac_busy_count--;
2N/A /* FALLTHRU */
2N/A case USB_AUDIO_MUTE:
2N/A default:
2N/A freemsg(mp);
2N/A break;
2N/A }
2N/A } else {
2N/A freemsg(mp);
2N/A }
2N/A
2N/A break;
2N/A default:
2N/A USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "M_CTL from unknown module(%s)",
2N/A ddi_driver_name(plumb_infop->acp_dip));
2N/A freemsg(mp);
2N/A }
2N/A
2N/A break;
2N/A default:
2N/A USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "Unknown type=%d", mp->b_datap->db_type);
2N/A usb_ac_free_mblk(mp);
2N/A }
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A /*
2N/A * Nobody is waiting; nothing to send up.
2N/A */
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_lrput: done");
2N/A
2N/A return (error);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Power Management
2N/A * usb_ac_power:
2N/A * power entry point
2N/A */
2N/Astatic int
2N/Ausb_ac_power(dev_info_t *dip, int comp, int level)
2N/A{
2N/A int instance = ddi_get_instance(dip);
2N/A usb_ac_state_t *uacp;
2N/A usb_ac_power_t *uacpm;
2N/A int rval = DDI_FAILURE;
2N/A
2N/A uacp = ddi_get_soft_state(usb_ac_statep, instance);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_PM, uacp->usb_ac_log_handle,
2N/A "usb_ac_power: comp=%d level=%d", comp, level);
2N/A
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A uacpm = uacp->usb_ac_pm;
2N/A
2N/A if (USB_DEV_PWRSTATE_OK(uacpm->acpm_pwr_states, level)) {
2N/A USB_DPRINTF_L2(PRINT_MASK_PM, uacp->usb_ac_log_handle,
2N/A "usb_ac_power: illegal level=%d pwr_states=%d",
2N/A level, uacpm->acpm_pwr_states);
2N/A
2N/A goto done;
2N/A }
2N/A
2N/A switch (level) {
2N/A case USB_DEV_OS_PWR_OFF:
2N/A rval = usb_ac_pwrlvl0(uacp);
2N/A break;
2N/A case USB_DEV_OS_PWR_1:
2N/A rval = usb_ac_pwrlvl1(uacp);
2N/A break;
2N/A case USB_DEV_OS_PWR_2:
2N/A rval = usb_ac_pwrlvl2(uacp);
2N/A break;
2N/A case USB_DEV_OS_FULL_PWR:
2N/A rval = usb_ac_pwrlvl3(uacp);
2N/A break;
2N/A }
2N/A
2N/Adone:
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * functions to handle power transition for various levels
2N/A * These functions act as place holders to issue USB commands
2N/A * to the devices to change their power levels
2N/A * Level 0 = Device is powered off
2N/A * Level 3 = Device if full powered
2N/A * Level 1,2 = Intermediate power level of the device as implemented
2N/A * by the hardware.
2N/A * Note that Level 0 is OS power-off and Level 3 is OS full-power.
2N/A */
2N/Astatic int
2N/Ausb_ac_pwrlvl0(usb_ac_state_t *uacp)
2N/A{
2N/A usb_ac_power_t *uacpm;
2N/A int rval;
2N/A
2N/A uacpm = uacp->usb_ac_pm;
2N/A
2N/A switch (uacp->usb_ac_dev_state) {
2N/A case USB_DEV_ONLINE:
2N/A /* Deny the powerdown request if the device is busy */
2N/A if (uacpm->acpm_pm_busy != 0) {
2N/A
2N/A return (USB_FAILURE);
2N/A }
2N/A
2N/A /* Issue USB D3 command to the device here */
2N/A rval = usb_set_device_pwrlvl3(uacp->usb_ac_dip);
2N/A ASSERT(rval == USB_SUCCESS);
2N/A
2N/A uacp->usb_ac_dev_state = USB_DEV_PWRED_DOWN;
2N/A uacpm->acpm_current_power = USB_DEV_OS_PWR_OFF;
2N/A
2N/A /* FALLTHRU */
2N/A case USB_DEV_DISCONNECTED:
2N/A case USB_DEV_SUSPENDED:
2N/A case USB_DEV_PWRED_DOWN:
2N/A default:
2N/A return (USB_SUCCESS);
2N/A }
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Ausb_ac_pwrlvl1(usb_ac_state_t *uacp)
2N/A{
2N/A int rval;
2N/A
2N/A /* Issue USB D2 command to the device here */
2N/A rval = usb_set_device_pwrlvl2(uacp->usb_ac_dip);
2N/A ASSERT(rval == USB_SUCCESS);
2N/A
2N/A return (USB_FAILURE);
2N/A}
2N/A
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Ausb_ac_pwrlvl2(usb_ac_state_t *uacp)
2N/A{
2N/A int rval;
2N/A
2N/A rval = usb_set_device_pwrlvl1(uacp->usb_ac_dip);
2N/A ASSERT(rval == USB_SUCCESS);
2N/A
2N/A return (USB_FAILURE);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Ausb_ac_pwrlvl3(usb_ac_state_t *uacp)
2N/A{
2N/A usb_ac_power_t *uacpm;
2N/A int rval;
2N/A
2N/A uacpm = uacp->usb_ac_pm;
2N/A
2N/A switch (uacp->usb_ac_dev_state) {
2N/A case USB_DEV_PWRED_DOWN:
2N/A /* Issue USB D0 command to the device here */
2N/A rval = usb_set_device_pwrlvl0(uacp->usb_ac_dip);
2N/A ASSERT(rval == USB_SUCCESS);
2N/A
2N/A uacp->usb_ac_dev_state = USB_DEV_ONLINE;
2N/A uacpm->acpm_current_power = USB_DEV_OS_FULL_PWR;
2N/A /* FALLTHRU */
2N/A case USB_DEV_ONLINE:
2N/A /* we are already in full power */
2N/A
2N/A /* FALLTHRU */
2N/A case USB_DEV_DISCONNECTED:
2N/A case USB_DEV_SUSPENDED:
2N/A
2N/A return (USB_SUCCESS);
2N/A default:
2N/A USB_DPRINTF_L2(PRINT_MASK_PM, uacp->usb_ac_log_handle,
2N/A "usb_ac_pwerlvl3: Illegal dev_state");
2N/A
2N/A return (USB_FAILURE);
2N/A }
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Ausb_ac_create_pm_components(dev_info_t *dip, usb_ac_state_t *uacp)
2N/A{
2N/A usb_ac_power_t *uacpm;
2N/A uint_t pwr_states;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_PM, uacp->usb_ac_log_handle,
2N/A "usb_ac_create_pm_components: begin");
2N/A
2N/A /* Allocate the state structure */
2N/A uacpm = kmem_zalloc(sizeof (usb_ac_power_t), KM_SLEEP);
2N/A uacp->usb_ac_pm = uacpm;
2N/A uacpm->acpm_state = uacp;
2N/A uacpm->acpm_capabilities = 0;
2N/A uacpm->acpm_current_power = USB_DEV_OS_FULL_PWR;
2N/A
2N/A if (usb_create_pm_components(dip, &pwr_states) ==
2N/A USB_SUCCESS) {
2N/A if (usb_handle_remote_wakeup(dip,
2N/A USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) {
2N/A uacpm->acpm_wakeup_enabled = 1;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_PM,
2N/A uacp->usb_ac_log_handle,
2N/A "remote Wakeup enabled");
2N/A }
2N/A uacpm->acpm_pwr_states = (uint8_t)pwr_states;
2N/A (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
2N/A } else {
2N/A if (uacpm) {
2N/A kmem_free(uacpm, sizeof (usb_ac_power_t));
2N/A uacp->usb_ac_pm = NULL;
2N/A }
2N/A USB_DPRINTF_L2(PRINT_MASK_PM, uacp->usb_ac_log_handle,
2N/A "pm not enabled");
2N/A }
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_PM, uacp->usb_ac_log_handle,
2N/A "usb_ac_create_pm_components: end");
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_plumb_ioctl:
2N/A * IOCTL issued from plumbing thread (only P_LINK_LH/P_UNLINK for now
2N/A * caused by ldi_ioctl). Maybe we will need to use this function
2N/A * to issue other IOCTLS to children in future from plumbing thread
2N/A */
2N/Astatic void
2N/Ausb_ac_plumb_ioctl(queue_t *q, mblk_t *mp)
2N/A{
2N/A int instance = audio_sup_get_qptr_instance(q);
2N/A usb_ac_state_t *uacp = ddi_get_soft_state(usb_ac_statep, instance);
2N/A struct iocblk *iocp;
2N/A struct linkblk *linkp;
2N/A int n;
2N/A usb_ac_streams_info_t *streams_infop;
2N/A
2N/A ASSERT(uacp != NULL);
2N/A ASSERT(mp != NULL);
2N/A ASSERT(mp->b_cont != NULL);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_plumb_ioctl, q=0x%p mp=0x%p instance=%d",
2N/A (void *)q, (void *)mp, instance);
2N/A
2N/A iocp = (struct iocblk *)mp->b_rptr;
2N/A mutex_enter(&uacp->usb_ac_mutex);
2N/A n = uacp->usb_ac_current_plumbed_index;
2N/A
2N/A switch (iocp->ioc_cmd) {
2N/A case I_PLINK:
2N/A linkp = (struct linkblk *)mp->b_cont->b_rptr;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "LINK ioctl, index=%d linkblk ptr=0x%p", n, (void *)linkp);
2N/A
2N/A /*
2N/A * We keep track of the module that is being
2N/A * currently plumbed through usb_ac_current_plumbed_index
2N/A * to the plumb structure array. We set the lwq field
2N/A * of the plumb structure here.
2N/A */
2N/A ASSERT(uacp->usb_ac_plumbed[n].acp_lwq == NULL);
2N/A uacp->usb_ac_plumbed[n].acp_lwq = linkp->l_qbot;
2N/A uacp->usb_ac_plumbed[n].acp_lrq = RD(linkp->l_qbot);
2N/A
2N/A audio_sup_set_qptr(uacp->usb_ac_plumbed[n].acp_lrq,
2N/A uacp->usb_ac_dev, (void *)uacp);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "index=%d lwq=0x%p lrq=0x%p", n, (void *)linkp->l_qbot,
2N/A (void *)RD(linkp->l_qbot));
2N/A break;
2N/A case I_UNLINK:
2N/A case I_PUNLINK:
2N/A linkp = (struct linkblk *)mp->b_cont->b_rptr;
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "UNLINK ioctl, linkblk ptr=0x%p", (void *)linkp);
2N/A
2N/A audio_sup_free_qptr(RD(linkp->l_qbot));
2N/A uacp->usb_ac_dev = 0;
2N/A
2N/A if (uacp->usb_ac_plumbed[n].acp_driver == USB_AS_PLUMBED) {
2N/A
2N/A /*
2N/A * we bzero the streams info and plumbed structure
2N/A * since there is no guarantee that the next plumbing
2N/A * will be identical
2N/A */
2N/A streams_infop = (usb_ac_streams_info_t *)
2N/A uacp->usb_ac_plumbed[n].acp_data;
2N/A cv_destroy(&(streams_infop->acs_ac_to_as_req.acr_cv));
2N/A
2N/A /* bzero the relevant plumbing structure */
2N/A bzero(streams_infop, sizeof (usb_ac_streams_info_t));
2N/A }
2N/A bzero(&uacp->usb_ac_plumbed[n], sizeof (usb_ac_plumbed_t));
2N/A
2N/A iocp->ioc_count = 0;
2N/A break;
2N/A default:
2N/A USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "Unknown ioctl, cmd=%d", iocp->ioc_cmd);
2N/A iocp->ioc_error = EINVAL;
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A goto iocnak;
2N/A }
2N/A
2N/A mutex_exit(&uacp->usb_ac_mutex);
2N/A
2N/A /*
2N/A * Common exit path for calls that return a positive
2N/A * acknowledgment with a return value of 0.
2N/A */
2N/A iocp->ioc_rval = 0;
2N/A iocp->ioc_error = 0;
2N/A mp->b_datap->db_type = M_IOCACK;
2N/A qreply(q, mp);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_plumb_ioctl: End (ACK)");
2N/A
2N/A return;
2N/A
2N/Aiocnak:
2N/A
2N/A iocp->ioc_rval = 0;
2N/A mp->b_datap->db_type = M_IOCNAK;
2N/A qreply(q, mp);
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_plumb_ioctl: End: (NAK)");
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_get_plumb_info:
2N/A * Get plumb_info pointer that matches module "name"
2N/A * If name = "usb_as", match the direction also (record or play)
2N/A */
2N/Astatic usb_ac_plumbed_t *
2N/Ausb_ac_get_plumb_info(usb_ac_state_t *uacp, char *name, uchar_t reg_play_type)
2N/A{
2N/A int n;
2N/A usb_ac_plumbed_t *plumb_infop = NULL;
2N/A usb_as_registration_t *asreg;
2N/A usb_ac_streams_info_t *asinfo;
2N/A
2N/A for (n = 0; n < USB_AC_MAX_PLUMBED; n++) {
2N/A if (uacp->usb_ac_plumbed[n].acp_dip == NULL) {
2N/A continue;
2N/A }
2N/A if (strcmp(ddi_driver_name(uacp->
2N/A usb_ac_plumbed[n].acp_dip), name) != 0) {
2N/A continue;
2N/A }
2N/A if (uacp->usb_ac_plumbed[n].acp_driver == USB_AS_PLUMBED) {
2N/A asinfo = uacp->usb_ac_plumbed[n].acp_data;
2N/A asreg = asinfo->acs_streams_reg;
2N/A /* Match direction */
2N/A if (asreg->reg_mode & reg_play_type) {
2N/A break;
2N/A }
2N/A } else if (uacp->usb_ac_plumbed[n].acp_driver ==
2N/A USB_AH_PLUMBED) {
2N/A break;
2N/A }
2N/A }
2N/A
2N/A if (n < USB_AC_MAX_PLUMBED) {
2N/A plumb_infop = &uacp->usb_ac_plumbed[n];
2N/A }
2N/A
2N/A return (plumb_infop);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_get_pinfo_from_lrq:
2N/A * Get plumb_info pointer that matches the lrq passed
2N/A */
2N/Astatic usb_ac_plumbed_t *
2N/Ausb_ac_get_plumb_info_from_lrq(usb_ac_state_t *uacp, queue_t *lrq)
2N/A{
2N/A int n;
2N/A
2N/A for (n = 0; n < USB_AC_MAX_PLUMBED; n++) {
2N/A if (uacp->usb_ac_plumbed[n].acp_lrq == lrq) {
2N/A
2N/A return (&uacp->usb_ac_plumbed[n]);
2N/A }
2N/A }
2N/A
2N/A return (NULL);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_get_featureID:
2N/A * find out if there is at least one feature unit that supports
2N/A * the request controls.
2N/A * Return featureID or USB_AC_ID_NONE.
2N/A */
2N/Astatic uint_t
2N/Ausb_ac_get_featureID(usb_ac_state_t *uacp, uchar_t dir,
2N/A uint_t channel, uint_t control)
2N/A{
2N/A uint_t count = 0;
2N/A
2N/A return (usb_ac_set_control(uacp, dir, USB_AUDIO_FEATURE_UNIT,
2N/A channel, control, USB_AC_FIND_ONE, &count, 0,
2N/A usb_ac_feature_unit_check));
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_feature_unit_check:
2N/A * check if a feature unit can support the required channel
2N/A * and control combination. Return USB_SUCCESS or USB_FAILURE.
2N/A * Called for each matching unit from usb_ac_traverse_connections.
2N/A */
2N/A/*ARGSUSED*/
2N/Astatic int
2N/Ausb_ac_feature_unit_check(usb_ac_state_t *uacp, uint_t featureID,
2N/A uint_t dir, uint_t channel, uint_t control, uint_t arg1, uint_t *depth)
2N/A{
2N/A usb_audio_feature_unit_descr1_t *feature_descrp;
2N/A int n_channel_controls;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_feature_unit_check: ID=%d ch=%d cntrl=%d",
2N/A featureID, channel, control);
2N/A
2N/A ASSERT((featureID >= 0) && (featureID < uacp->usb_ac_max_unit));
2N/A
2N/A /*
2N/A * check if this control is supported on this channel
2N/A */
2N/A feature_descrp = (usb_audio_feature_unit_descr1_t *)
2N/A uacp->usb_ac_units[featureID].acu_descriptor;
2N/A ASSERT(feature_descrp->bUnitID == featureID);
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "bControlSize=%d", feature_descrp->bControlSize);
2N/A
2N/A if (feature_descrp->bControlSize == 0) {
2N/A featureID = USB_AC_ID_NONE;
2N/A } else {
2N/A uint_t index;
2N/A
2N/A n_channel_controls = (feature_descrp->bLength -
2N/A offsetof(usb_audio_feature_unit_descr1_t,
2N/A bmaControls))/feature_descrp->bControlSize;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ALL,
2N/A uacp->usb_ac_log_handle,
2N/A "#controls: %d index=%d", n_channel_controls,
2N/A feature_descrp->bControlSize * channel);
2N/A
2N/A if (channel > n_channel_controls) {
2N/A featureID = USB_AC_ID_NONE;
2N/A } else {
2N/A /*
2N/A * we only support MUTE and VOLUME
2N/A * which are in the first byte
2N/A */
2N/A index = feature_descrp->bControlSize *
2N/A channel;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ALL,
2N/A uacp->usb_ac_log_handle,
2N/A "control: 0x%x",
2N/A feature_descrp->bmaControls[index]);
2N/A
2N/A if ((feature_descrp->bmaControls[index] &
2N/A control) == 0) {
2N/A featureID = USB_AC_ID_NONE;
2N/A }
2N/A }
2N/A }
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
2N/A "usb_ac_feature_unit_check: dir=%d featureID=0x%x",
2N/A dir, featureID);
2N/A
2N/A return ((featureID != USB_AC_ID_NONE) ?
2N/A USB_SUCCESS : USB_FAILURE);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Descriptor Management
2N/A *
2N/A * usb_ac_handle_descriptors:
2N/A * extract interesting descriptors from the config cloud
2N/A */
2N/Astatic int
2N/Ausb_ac_handle_descriptors(usb_ac_state_t *uacp)
2N/A{
2N/A int rest, len, index;
2N/A int rval = USB_FAILURE;
2N/A usb_audio_cs_if_descr_t descr;
2N/A usb_client_dev_data_t *dev_data = uacp->usb_ac_dev_data;
2N/A usb_alt_if_data_t *altif_data;
2N/A usb_cvs_data_t *cvs;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "config=%ld, interface=%d",
2N/A (long)(dev_data->dev_curr_cfg - &dev_data->dev_cfg[0]),
2N/A dev_data->dev_curr_if);
2N/A
2N/A altif_data = &dev_data->dev_curr_cfg->
2N/A cfg_if[dev_data->dev_curr_if].if_alt[0];
2N/A
2N/A uacp->usb_ac_ifno = dev_data->dev_curr_if;
2N/A uacp->usb_ac_if_descr = altif_data->altif_descr;
2N/A
2N/A /* find USB_AUDIO_CS_INTERFACE type descriptor */
2N/A for (index = 0; index < altif_data->altif_n_cvs; index++) {
2N/A cvs = &altif_data->altif_cvs[index];
2N/A if (cvs->cvs_buf == NULL) {
2N/A continue;
2N/A }
2N/A if (cvs->cvs_buf[1] == USB_AUDIO_CS_INTERFACE) {
2N/A break;
2N/A }
2N/A }
2N/A
2N/A if (index == altif_data->altif_n_cvs) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "cannot find descriptor type %d", USB_AUDIO_CS_INTERFACE);
2N/A
2N/A return (rval);
2N/A }
2N/A
2N/A len = usb_parse_data(
2N/A CS_AC_IF_HEADER_FORMAT,
2N/A cvs->cvs_buf, cvs->cvs_buf_len,
2N/A (void *)&descr, sizeof (usb_audio_cs_if_descr_t));
2N/A
2N/A /* is this a sane header descriptor */
2N/A if (!((len >= CS_AC_IF_HEADER_SIZE) &&
2N/A (descr.bDescriptorType == USB_AUDIO_CS_INTERFACE) &&
2N/A (descr.bDescriptorSubType == USB_AUDIO_HEADER))) {
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "invalid header");
2N/A
2N/A return (rval);
2N/A }
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "header: type=0x%x subtype=0x%x bcdADC=0x%x\n\t"
2N/A "total=0x%x InCol=0x%x",
2N/A descr.bDescriptorType,
2N/A descr.bDescriptorSubType,
2N/A descr.bcdADC,
2N/A descr.wTotalLength,
2N/A descr.blnCollection);
2N/A
2N/A /*
2N/A * we read descriptors by index and store them in ID array.
2N/A * the actual parsing is done in usb_ac_add_unit_descriptor()
2N/A */
2N/A rest = descr.wTotalLength - descr.bLength;
2N/A for (index++; rest > 0; index++) {
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "index=%d rest=%d", index, rest);
2N/A
2N/A cvs = &altif_data->altif_cvs[index];
2N/A if (cvs->cvs_buf == NULL) {
2N/A continue;
2N/A }
2N/A
2N/A /* add to ID array */
2N/A usb_ac_add_unit_descriptor(uacp, cvs->cvs_buf,
2N/A cvs->cvs_buf_len);
2N/A rest -= cvs->cvs_buf[0];
2N/A }
2N/A rval = USB_SUCCESS;
2N/A
2N/A usb_ac_setup_connections(uacp);
2N/A
2N/A /* determine port types */
2N/A usb_ac_map_termtype_to_port(uacp, AUDIO_PLAY);
2N/A usb_ac_map_termtype_to_port(uacp, AUDIO_RECORD);
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "input port types=0x%x output port types =0x%x",
2N/A uacp->usb_ac_input_ports, uacp->usb_ac_output_ports);
2N/A
2N/A
2N/A return (rval);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_setup_connections:
2N/A * build a matrix reflecting all connections
2N/A */
2N/Astatic void
2N/Ausb_ac_setup_connections(usb_ac_state_t *uacp)
2N/A{
2N/A usb_ac_unit_list_t *units = uacp->usb_ac_units;
2N/A uchar_t *a, **p, i, unit;
2N/A size_t a_len, p_len;
2N/A
2N/A /* allocate array for unit types for quick reference */
2N/A uacp->usb_ac_unit_type = kmem_zalloc(uacp->usb_ac_max_unit,
2N/A KM_SLEEP);
2N/A /* allocate array for traversal path */
2N/A uacp->usb_ac_traverse_path = kmem_zalloc(uacp->usb_ac_max_unit,
2N/A KM_SLEEP);
2N/A
2N/A
2N/A /* allocate the connection matrix and set it up */
2N/A a_len = uacp->usb_ac_max_unit * uacp->usb_ac_max_unit;
2N/A p_len = uacp->usb_ac_max_unit * sizeof (uchar_t *);
2N/A
2N/A /* trick to create a 2 dimensional array */
2N/A a = kmem_zalloc(a_len, KM_SLEEP);
2N/A p = kmem_zalloc(p_len, KM_SLEEP);
2N/A for (i = 0; i < uacp->usb_ac_max_unit; i++) {
2N/A p[i] = a + i * uacp->usb_ac_max_unit;
2N/A }
2N/A uacp->usb_ac_connections = p;
2N/A uacp->usb_ac_connections_len = p_len;
2N/A uacp->usb_ac_connections_a = a;
2N/A uacp->usb_ac_connections_a_len = a_len;
2N/A
2N/A /* traverse all units and set connections */
2N/A for (unit = 0; unit < uacp->usb_ac_max_unit; unit++) {
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "traversing unit=0x%x type=0x%x",
2N/A unit, units[unit].acu_type);
2N/A
2N/A /* store type in the first unused column */
2N/A uacp->usb_ac_unit_type[unit] = units[unit].acu_type;
2N/A
2N/A /* save the Unit ID in the unit it points to */
2N/A switch (units[unit].acu_type) {
2N/A case USB_AUDIO_FEATURE_UNIT:
2N/A {
2N/A usb_audio_feature_unit_descr1_t *d =
2N/A units[unit].acu_descriptor;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "sourceID=0x%x type=0x%x", d->bSourceID,
2N/A units[d->bSourceID].acu_type);
2N/A
2N/A if (d->bSourceID != 0) {
2N/A ASSERT(p[unit][d->bSourceID] == B_FALSE);
2N/A p[unit][d->bSourceID] = B_TRUE;
2N/A }
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_OUTPUT_TERMINAL:
2N/A {
2N/A usb_audio_output_term_descr_t *d =
2N/A units[unit].acu_descriptor;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "sourceID=0x%x type=0x%x", d->bSourceID,
2N/A units[d->bSourceID].acu_type);
2N/A
2N/A if (d->bSourceID != 0) {
2N/A ASSERT(p[unit][d->bSourceID] == B_FALSE);
2N/A p[unit][d->bSourceID] = B_TRUE;
2N/A }
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_MIXER_UNIT:
2N/A {
2N/A usb_audio_mixer_unit_descr1_t *d =
2N/A units[unit].acu_descriptor;
2N/A int n_sourceID = d->bNrInPins;
2N/A int id;
2N/A
2N/A for (id = 0; id < n_sourceID; id++) {
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "sourceID=0x%x type=0x%x c=%d",
2N/A d->baSourceID[id],
2N/A units[d->baSourceID[id]].acu_type,
2N/A p[unit][d->baSourceID[id]]);
2N/A
2N/A if (d->baSourceID[id] != 0) {
2N/A ASSERT(p[unit][d->baSourceID[id]] ==
2N/A B_FALSE);
2N/A p[unit][d->baSourceID[id]] = B_TRUE;
2N/A }
2N/A }
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_SELECTOR_UNIT:
2N/A {
2N/A usb_audio_selector_unit_descr1_t *d =
2N/A units[unit].acu_descriptor;
2N/A int n_sourceID = d->bNrInPins;
2N/A int id;
2N/A
2N/A for (id = 0; id < n_sourceID; id++) {
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "sourceID=0x%x type=0x%x",
2N/A d->baSourceID[id],
2N/A units[d->baSourceID[id]].acu_type);
2N/A
2N/A if (d->baSourceID[id] != 0) {
2N/A ASSERT(p[unit][d->baSourceID[id]] ==
2N/A B_FALSE);
2N/A p[unit][d->baSourceID[id]] = B_TRUE;
2N/A }
2N/A }
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_PROCESSING_UNIT:
2N/A {
2N/A usb_audio_mixer_unit_descr1_t *d =
2N/A units[unit].acu_descriptor;
2N/A int n_sourceID = d->bNrInPins;
2N/A int id;
2N/A
2N/A for (id = 0; id < n_sourceID; id++) {
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "sourceID=0x%x type=0x%x",
2N/A d->baSourceID[id],
2N/A units[d->baSourceID[id]].acu_type);
2N/A
2N/A if (d->baSourceID[id] != 0) {
2N/A ASSERT(p[unit][d->baSourceID[id]] ==
2N/A B_FALSE);
2N/A p[unit][d->baSourceID[id]] = B_TRUE;
2N/A }
2N/A }
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_EXTENSION_UNIT:
2N/A {
2N/A usb_audio_extension_unit_descr1_t *d =
2N/A units[unit].acu_descriptor;
2N/A int n_sourceID = d->bNrInPins;
2N/A int id;
2N/A
2N/A for (id = 0; id < n_sourceID; id++) {
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "sourceID=0x%x type=0x%x",
2N/A d->baSourceID[id],
2N/A units[d->baSourceID[id]].acu_type);
2N/A
2N/A if (d->baSourceID[id] != 0) {
2N/A ASSERT(p[unit][d->baSourceID[id]] ==
2N/A B_TRUE);
2N/A p[unit][d->baSourceID[id]] = B_FALSE;
2N/A }
2N/A }
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_INPUT_TERMINAL:
2N/A
2N/A break;
2N/A default:
2N/A /*
2N/A * Ignore the rest because they are not support yet
2N/A */
2N/A break;
2N/A }
2N/A }
2N/A
2N/A#ifdef DEBUG
2N/A /* display topology in log buffer */
2N/A{
2N/A uint_t i, j, l;
2N/A char *buf;
2N/A
2N/A l = uacp->usb_ac_max_unit * 5;
2N/A
2N/A buf = kmem_alloc(l, KM_SLEEP);
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "unit types:");
2N/A
2N/A /* two strings so they won't be replaced accidentily by tab */
2N/A (void) sprintf(&buf[0], " "" ");
2N/A for (i = 1; i < uacp->usb_ac_max_unit; i++) {
2N/A (void) sprintf(&buf[2 + (i*3)], "%02d ", i);
2N/A }
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf);
2N/A
2N/A (void) sprintf(&buf[0], " +-------");
2N/A for (i = 1; i < uacp->usb_ac_max_unit; i++) {
2N/A (void) sprintf(&buf[5+((i-1)*3)], "---");
2N/A }
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf);
2N/A
2N/A (void) sprintf(&buf[0], " "" ");
2N/A for (i = 1; i < uacp->usb_ac_max_unit; i++) {
2N/A (void) sprintf(&buf[2 + (i*3)], "%02d ",
2N/A uacp->usb_ac_unit_type[i]);
2N/A }
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf);
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, " ");
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "adjacency matrix:");
2N/A (void) sprintf(&buf[0], " "" ");
2N/A for (i = 1; i < uacp->usb_ac_max_unit; i++) {
2N/A (void) sprintf(&buf[2 + (i*3)], "%02d ", i);
2N/A }
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf);
2N/A
2N/A (void) sprintf(&buf[0], " +-------");
2N/A for (i = 1; i < uacp->usb_ac_max_unit; i++) {
2N/A (void) sprintf(&buf[5+((i-1)*3)], "---");
2N/A }
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf);
2N/A
2N/A for (i = 1; i < uacp->usb_ac_max_unit; i++) {
2N/A (void) sprintf(&buf[0], "%02d| "" ", i);
2N/A for (j = 1; j < uacp->usb_ac_max_unit; j++) {
2N/A (void) sprintf(&buf[1+(j * 3)], "%2d ", p[i][j]);
2N/A }
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf);
2N/A }
2N/A kmem_free(buf, l);
2N/A}
2N/A#endif
2N/A}
2N/A
2N/A
2N/A/*
2N/A * usb_ac_add_unit_descriptor:
2N/A * take the parsed descriptor in the buffer and store it in the ID unit
2N/A * array. we grow the unit array if the ID exceeds the current max
2N/A */
2N/Astatic void
2N/Ausb_ac_add_unit_descriptor(usb_ac_state_t *uacp, uchar_t *buffer,
2N/A size_t buflen)
2N/A{
2N/A void *descr;
2N/A int len;
2N/A char *format;
2N/A size_t size;
2N/A
2N/A USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
2N/A "usb_ac_add_unit_descriptor: 0x%x 0x%x 0x%x",
2N/A buffer[0], buffer[1], buffer[2]);
2N/A
2N/A /* doubling the length should allow for padding */
2N/A len = 2 * buffer[0];
2N/A descr = kmem_zalloc(len, KM_SLEEP);
2N/A
2N/A switch (buffer[2]) {
2N/A case USB_AUDIO_INPUT_TERMINAL:
2N/A format = CS_AC_INPUT_TERM_FORMAT;
2N/A size = CS_AC_INPUT_TERM_SIZE;
2N/A
2N/A break;
2N/A case USB_AUDIO_OUTPUT_TERMINAL:
2N/A format = CS_AC_OUTPUT_TERM_FORMAT;
2N/A size = CS_AC_OUTPUT_TERM_SIZE;
2N/A
2N/A break;
2N/A case USB_AUDIO_MIXER_UNIT:
2N/A format = CS_AC_MIXER_UNIT_DESCR1_FORMAT "255c";
2N/A size = CS_AC_MIXER_UNIT_DESCR1_SIZE + buffer[4] - 1;
2N/A
2N/A break;
2N/A case USB_AUDIO_SELECTOR_UNIT:
2N/A format = CS_AC_SELECTOR_UNIT_DESCR1_FORMAT "255c";
2N/A size = CS_AC_SELECTOR_UNIT_DESCR1_SIZE + buffer[4] - 1;
2N/A
2N/A break;
2N/A case USB_AUDIO_FEATURE_UNIT:
2N/A format = CS_AC_FEATURE_UNIT_FORMAT "255c";
2N/A size = CS_AC_FEATURE_UNIT_SIZE;
2N/A
2N/A break;
2N/A case USB_AUDIO_PROCESSING_UNIT:
2N/A format = CS_AC_PROCESSING_UNIT_DESCR1_FORMAT "255c";
2N/A size = CS_AC_PROCESSING_UNIT_DESCR1_SIZE + buffer[6] - 1;
2N/A
2N/A break;
2N/A case USB_AUDIO_EXTENSION_UNIT:
2N/A format = CS_AC_EXTENSION_UNIT_DESCR1_FORMAT "255c";
2N/A size = CS_AC_EXTENSION_UNIT_DESCR1_SIZE + buffer[6] - 1;
2N/A
2N/A break;
2N/A default:
2N/A USB_DPRINTF_L2(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "unsupported descriptor %d", buffer[2]);
2N/A
2N/A /* ignore this descriptor */
2N/A kmem_free(descr, len);
2N/A
2N/A return;
2N/A }
2N/A
2N/A if (usb_parse_data(format, buffer, buflen, descr, len) < size) {
2N/A /* ignore this descriptor */
2N/A kmem_free(descr, len);
2N/A
2N/A return;
2N/A }
2N/A
2N/A switch (buffer[2]) {
2N/A case USB_AUDIO_INPUT_TERMINAL:
2N/A {
2N/A usb_audio_input_term_descr_t *d =
2N/A (usb_audio_input_term_descr_t *)descr;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "input term: type=0x%x sub=0x%x termid=0x%x\n\t"
2N/A "termtype=0x%x assoc=0x%x #ch=%d "
2N/A "chconf=0x%x ich=0x%x iterm=0x%x",
2N/A d->bDescriptorType, d->bDescriptorSubType,
2N/A d->bTerminalID, d->wTerminalType,
2N/A d->bAssocTerminal, d->bNrChannels,
2N/A d->wChannelConfig, d->iChannelNames,
2N/A d->iTerminal);
2N/A
2N/A usb_ac_alloc_unit(uacp, d->bTerminalID);
2N/A uacp->usb_ac_units[d->bTerminalID].acu_descriptor = descr;
2N/A uacp->usb_ac_units[d->bTerminalID].acu_type = buffer[2];
2N/A uacp->usb_ac_units[d->bTerminalID].acu_descr_length = len;
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_OUTPUT_TERMINAL:
2N/A {
2N/A usb_audio_output_term_descr_t *d =
2N/A (usb_audio_output_term_descr_t *)descr;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "output term: type=0x%x sub=0x%x termid=0x%x\n\t"
2N/A "termtype=0x%x assoc=0x%x sourceID=0x%x iterm=0x%x",
2N/A d->bDescriptorType, d->bDescriptorSubType,
2N/A d->bTerminalID, d->wTerminalType,
2N/A d->bAssocTerminal, d->bSourceID,
2N/A d->iTerminal);
2N/A
2N/A usb_ac_alloc_unit(uacp, d->bTerminalID);
2N/A uacp->usb_ac_units[d->bTerminalID].acu_descriptor = descr;
2N/A uacp->usb_ac_units[d->bTerminalID].acu_type = buffer[2];
2N/A uacp->usb_ac_units[d->bTerminalID].acu_descr_length = len;
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_MIXER_UNIT:
2N/A {
2N/A usb_audio_mixer_unit_descr1_t *d =
2N/A (usb_audio_mixer_unit_descr1_t *)descr;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "mixer unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
2N/A "#pins=0x%x sourceid[0]=0x%x",
2N/A d->bDescriptorType, d->bDescriptorSubType,
2N/A d->bUnitID, d->bNrInPins, d->baSourceID[0]);
2N/A usb_ac_alloc_unit(uacp, d->bUnitID);
2N/A uacp->usb_ac_units[d->bUnitID].acu_descriptor = descr;
2N/A uacp->usb_ac_units[d->bUnitID].acu_type = buffer[2];
2N/A uacp->usb_ac_units[d->bUnitID].acu_descr_length = len;
2N/A
2N/A break;
2N/A }
2N/A case USB_AUDIO_SELECTOR_UNIT:
2N/A {
2N/A usb_audio_selector_unit_descr1_t *d =
2N/A (usb_audio_selector_unit_descr1_t *)descr;
2N/A
2N/A USB_DPRINTF_L3(PRINT_MASK_ATTA,
2N/A uacp->usb_ac_log_handle,
2N/A "selector unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
2N/A "#pins=0x%x sourceid[0]=0x%x",
2N/A d->bDescriptorType, d->bDescriptorSubType,
2N/A d->bUnitID, d->bNrInPins, d->baSourceID[0]);
usb_ac_alloc_unit(uacp, d->bUnitID);
uacp->usb_ac_units[d->bUnitID].acu_descriptor = descr;
uacp->usb_ac_units[d->bUnitID].acu_type = buffer[2];
uacp->usb_ac_units[d->bUnitID].acu_descr_length = len;
break;
}
case USB_AUDIO_FEATURE_UNIT:
{
usb_audio_feature_unit_descr1_t *d =
(usb_audio_feature_unit_descr1_t *)descr;
USB_DPRINTF_L3(PRINT_MASK_ATTA,
uacp->usb_ac_log_handle,
"feature unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"sourceid=0x%x size=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
d->bUnitID, d->bSourceID, d->bControlSize);
usb_ac_alloc_unit(uacp, d->bUnitID);
uacp->usb_ac_units[d->bUnitID].acu_descriptor = descr;
uacp->usb_ac_units[d->bUnitID].acu_type = buffer[2];
uacp->usb_ac_units[d->bUnitID].acu_descr_length = len;
break;
}
case USB_AUDIO_PROCESSING_UNIT:
{
usb_audio_processing_unit_descr1_t *d =
(usb_audio_processing_unit_descr1_t *)descr;
USB_DPRINTF_L3(PRINT_MASK_ATTA,
uacp->usb_ac_log_handle,
"processing unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"#pins=0x%x sourceid[0]=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
d->bUnitID, d->bNrInPins, d->baSourceID[0]);
usb_ac_alloc_unit(uacp, d->bUnitID);
uacp->usb_ac_units[d->bUnitID].acu_descriptor = descr;
uacp->usb_ac_units[d->bUnitID].acu_type = buffer[2];
uacp->usb_ac_units[d->bUnitID].acu_descr_length = len;
break;
}
case USB_AUDIO_EXTENSION_UNIT:
{
usb_audio_extension_unit_descr1_t *d =
(usb_audio_extension_unit_descr1_t *)descr;
USB_DPRINTF_L3(PRINT_MASK_ATTA,
uacp->usb_ac_log_handle,
"mixer unit: type=0x%x sub=0x%x unitid=0x%x\n\t"
"#pins=0x%x sourceid[0]=0x%x",
d->bDescriptorType, d->bDescriptorSubType,
d->bUnitID, d->bNrInPins, d->baSourceID[0]);
usb_ac_alloc_unit(uacp, d->bUnitID);
uacp->usb_ac_units[d->bUnitID].acu_descriptor = descr;
uacp->usb_ac_units[d->bUnitID].acu_type = buffer[2];
uacp->usb_ac_units[d->bUnitID].acu_descr_length = len;
break;
}
default:
break;
}
}
/*
* usb_ac_alloc_unit:
* check if the unit ID is less than max_unit in which case no
* extra entries are needed. If more entries are needed, copy over
* the existing array into a new larger array
*/
static void
usb_ac_alloc_unit(usb_ac_state_t *uacp, uint_t unit)
{
usb_ac_unit_list_t *old = NULL;
uint_t max_unit;
USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
"usb_ac_alloc_unit: unit=%d", unit);
if (uacp->usb_ac_units) {
if (unit < uacp->usb_ac_max_unit) {
/* existing array is big enough */
return;
}
old = uacp->usb_ac_units;
max_unit = uacp->usb_ac_max_unit;
}
/* allocate two extra ones */
unit += 2;
uacp->usb_ac_max_unit = unit;
uacp->usb_ac_units = kmem_zalloc(unit *
sizeof (usb_ac_unit_list_t), KM_SLEEP);
if (old) {
size_t len = max_unit * sizeof (usb_ac_unit_list_t);
bcopy(old, uacp->usb_ac_units, len);
kmem_free(old, len);
}
}
/*
* usb_ac_free_all_units:
* free the entire unit list
*/
static void
usb_ac_free_all_units(usb_ac_state_t *uacp)
{
uint_t unit;
usb_ac_unit_list_t *unitp;
if (uacp->usb_ac_units == NULL) {
return;
}
USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
"usb_ac_alloc_unit: max_unit=%d", uacp->usb_ac_max_unit);
for (unit = 0; unit < uacp->usb_ac_max_unit; unit++) {
unitp = &uacp->usb_ac_units[unit];
if (unitp) {
if (unitp->acu_descriptor) {
kmem_free(unitp->acu_descriptor,
unitp->acu_descr_length);
}
}
}
kmem_free(uacp->usb_ac_units, uacp->usb_ac_max_unit *
sizeof (usb_ac_unit_list_t));
}
/*
* usb_ac_lookup_port_type:
* map term type to port type
* default just return LINE_IN + LINE_OUT
*/
static int
usb_ac_lookup_port_type(ushort_t termtype)
{
uint_t i;
for (i = 0; ; i++) {
if (usb_ac_term_type_map[i].term_type == 0) {
break;
}
if (usb_ac_term_type_map[i].term_type == termtype) {
return (usb_ac_term_type_map[i].port_type);
}
}
return (AUDIO_LINE_IN|AUDIO_LINE_OUT);
}
/*
* usb_ac_update_port:
* called for each terminal
*/
/*ARGSUSED*/
static int
usb_ac_update_port(usb_ac_state_t *uacp, uint_t id,
uint_t dir, uint_t channel, uint_t control, uint_t arg1, uint_t *depth)
{
if (dir & AUDIO_PLAY) {
usb_audio_output_term_descr_t *d =
(usb_audio_output_term_descr_t *)
uacp->usb_ac_units[id].acu_descriptor;
uint_t port_type =
usb_ac_lookup_port_type(d->wTerminalType);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_update_port: dir=%d type=0x%x port type=%d",
dir, d->wTerminalType, port_type);
uacp->usb_ac_output_ports |= port_type;
uacp->usb_ac_output_ports &= ~AUDIO_LINE_IN;
} else {
usb_audio_output_term_descr_t *d =
(usb_audio_output_term_descr_t *)
uacp->usb_ac_units[id].acu_descriptor;
uint_t port_type =
usb_ac_lookup_port_type(d->wTerminalType);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_update_port: dir=%d type=0x%x port type=%d",
dir, d->wTerminalType, port_type);
uacp->usb_ac_input_ports |=
usb_ac_lookup_port_type(d->wTerminalType);
uacp->usb_ac_input_ports &= ~AUDIO_LINE_OUT;
}
return (USB_SUCCESS);
}
/*
* usb_ac_map_termtype_to_port:
* starting from a streaming termtype find all
* input or output terminals and OR into uacp->usb_ac_input_ports
* or uacp->usb_ac_output_ports;
*/
static void
usb_ac_map_termtype_to_port(usb_ac_state_t *uacp, uint_t dir)
{
uint_t count = 0;
uint_t depth = 0;
uint_t search_type = (dir & AUDIO_PLAY) ?
USB_AUDIO_OUTPUT_TERMINAL : USB_AUDIO_INPUT_TERMINAL;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_map_term_to_port: dir=%d", dir);
(void) usb_ac_traverse_all_units(uacp, dir, search_type, 0,
0, USB_AC_FIND_ALL, &count, 0, &depth, usb_ac_update_port);
ASSERT(depth == 0);
}
/*
* usb_ac_set_port:
* find a selector port (record side only) and set the
* input to the matching pin
*/
static uint_t
usb_ac_set_port(usb_ac_state_t *uacp, uint_t dir, uint_t port)
{
uint_t count = 0;
uint_t id;
uint_t depth = 0;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_port: dir=%d port=%d", dir, port);
/* we only support the selector for the record side */
if (dir & AUDIO_RECORD) {
id = usb_ac_traverse_all_units(uacp, dir,
USB_AUDIO_SELECTOR_UNIT, 0,
0, USB_AC_FIND_ONE, &count, port, &depth,
usb_ac_set_selector);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_port: id=%d count=%d port=%d",
id, count, port);
ASSERT(depth == 0);
}
return (USB_SUCCESS);
}
/*
* usb_ac_match_port:
* given the requested port type, find a correspondig term type
* Called from usb_ac_traverse_all_units()
*/
/*ARGSUSED*/
static int
usb_ac_match_port(usb_ac_state_t *uacp, uint_t id,
uint_t dir, uint_t channel, uint_t control, uint_t arg1, uint_t *depth)
{
uint_t port_type;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_match_port: id=%d dir=%d port=%d",
id, dir, arg1);
if (dir & AUDIO_PLAY) {
usb_audio_output_term_descr_t *d =
(usb_audio_output_term_descr_t *)
uacp->usb_ac_units[id].acu_descriptor;
port_type = usb_ac_lookup_port_type(d->wTerminalType);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_match_port: "
"dir=%d type=0x%x port_type=%d port=%d",
dir, d->wTerminalType, port_type, arg1);
} else {
usb_audio_output_term_descr_t *d =
(usb_audio_output_term_descr_t *)
uacp->usb_ac_units[id].acu_descriptor;
port_type = usb_ac_lookup_port_type(d->wTerminalType);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_match_port: "
"dir=%d type=0x%x port_type=%d port=%d",
dir, d->wTerminalType, port_type, arg1);
}
return ((port_type & arg1) ? USB_SUCCESS : USB_FAILURE);
}
/*
* usb_ac_set_selector:
* Called from usb_ac_traverse_all_units()
* Find the correct pin and set selector to this pin
*/
/*ARGSUSED*/
static int
usb_ac_set_selector(usb_ac_state_t *uacp, uint_t id,
uint_t dir, uint_t channel, uint_t control, uint_t arg1, uint_t *depth)
{
uint_t count = 0;
uint_t unit = USB_AC_ID_NONE;
uint_t pin;
uint_t search_target =
(dir & AUDIO_PLAY) ? USB_AUDIO_OUTPUT_TERMINAL :
USB_AUDIO_INPUT_TERMINAL;
usb_audio_selector_unit_descr1_t *d =
(usb_audio_selector_unit_descr1_t *)
uacp->usb_ac_units[id].acu_descriptor;
int n_sourceID = d->bNrInPins;
int rval = USB_FAILURE;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_selector: id=%d dir=%d port=%d",
id, dir, arg1);
/*
* for each pin, find a term type that matches the
* requested port type
*/
for (pin = 0; pin < n_sourceID; pin++) {
if (d->baSourceID[pin] == 0) {
break;
}
unit = d->baSourceID[pin];
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_selector: pin=%d unit=%d", pin, unit);
if (uacp->usb_ac_unit_type[unit] == search_target) {
if (usb_ac_match_port(uacp, unit, dir, channel,
control, arg1, depth) == USB_SUCCESS) {
break;
} else {
unit = USB_AC_ID_NONE;
continue;
}
}
/* find units connected to this unit */
unit = usb_ac_traverse_connections(uacp, unit,
dir, search_target, channel, control,
USB_AC_FIND_ONE, &count, arg1, depth,
usb_ac_match_port);
if (unit != USB_AC_ID_NONE) {
break;
}
}
if (unit != USB_AC_ID_NONE) {
mblk_t *data;
usb_cr_t cr;
usb_cb_flags_t cb_flags;
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_selector: found id=%d at pin %d", unit, pin);
mutex_exit(&uacp->usb_ac_mutex);
data = allocb_wait(1, BPRI_HI, STR_NOSIG, NULL);
/* pins are 1-based */
*(data->b_rptr) = (char)++pin;
if (usb_pipe_sync_ctrl_xfer(
uacp->usb_ac_dip,
uacp->usb_ac_default_ph,
USB_DEV_REQ_HOST_TO_DEV |
USB_DEV_REQ_TYPE_CLASS |
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
USB_AUDIO_SET_CUR, /* bRequest */
0, /* wValue */
/* feature unit and id */
(id << 8)| uacp->usb_ac_ifno, /* wIndex */
1, /* wLength */
&data,
USB_ATTRS_NONE,
&cr, &cb_flags,
USB_FLAGS_SLEEP) == USB_SUCCESS) {
USB_DPRINTF_L3(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"set current selection: %d", *data->b_rptr);
rval = USB_SUCCESS;
} else {
USB_DPRINTF_L2(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"set current pin selection failed");
}
freemsg(data);
mutex_enter(&uacp->usb_ac_mutex);
} else {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_selector: nothing found");
}
return (rval);
}
/*
* usb_ac_set_control:
* apply func to all units of search_target type for both the
* requested channel and master channel
*/
static uint_t
usb_ac_set_control(usb_ac_state_t *uacp, uint_t dir, uint_t search_target,
uint_t channel, uint_t control, uint_t all_or_one,
uint_t *count, uint_t arg1,
int (*func)(usb_ac_state_t *uacp, uint_t unit, uint_t dir,
uint_t channel, uint_t control, uint_t arg1, uint_t *depth))
{
uint_t id;
uint_t depth = 0;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_control: dir=%d type=%d ch=%d cntl=%d",
dir, search_target, channel, control);
id = usb_ac_traverse_all_units(uacp, dir, search_target, channel,
control, all_or_one, count, arg1, &depth, func);
if ((channel != 0) &&
(((id == USB_AC_ID_NONE) && (all_or_one == USB_AC_FIND_ONE)) ||
(all_or_one == USB_AC_FIND_ALL))) {
/* try master channel */
channel = 0;
id = usb_ac_traverse_all_units(uacp, dir, search_target,
channel, control, all_or_one, count, arg1,
&depth, func);
}
ASSERT(depth == 0);
return (id);
}
/*
* usb_ac_traverse_all_units:
* traverse all units starting with all IT or OT depending on direction.
* If no unit is found for the particular channel, try master channel
* If a matching unit is found, apply the function passed by
* the caller
*/
static uint_t
usb_ac_traverse_all_units(usb_ac_state_t *uacp, uint_t dir,
uint_t search_target, uint_t channel, uint_t control,
uint_t all_or_one, uint_t *count, uint_t arg1, uint_t *depth,
int (*func)(usb_ac_state_t *uacp, uint_t unit, uint_t dir,
uint_t channel, uint_t control, uint_t arg1, uint_t *depth))
{
uint_t unit, start_type, id;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_traverse_all_units: "
"dir=%d type=%d ch=%d cntl=%d all=%d depth=%d",
dir, search_target, channel, control, all_or_one, *depth);
start_type = (dir & AUDIO_PLAY) ? USB_AUDIO_INPUT_TERMINAL :
USB_AUDIO_OUTPUT_TERMINAL;
/* keep track of recursion */
if ((*depth)++ > USB_AC_MAX_DEPTH) {
USB_DPRINTF_L1(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"Unit topology too complex, giving up");
return (USB_AC_ID_NONE);
}
for (unit = 1; unit < uacp->usb_ac_max_unit; unit++) {
/* is this an IT or OT? */
if (uacp->usb_ac_unit_type[unit] != start_type) {
continue;
}
/* start at streaming term types */
if (dir & AUDIO_PLAY) {
usb_audio_input_term_descr_t *d =
uacp->usb_ac_units[unit].acu_descriptor;
if (d->wTerminalType !=
USB_AUDIO_TERM_TYPE_STREAMING) {
continue;
}
} else {
usb_audio_output_term_descr_t *d =
uacp->usb_ac_units[unit].acu_descriptor;
if (d->wTerminalType !=
USB_AUDIO_TERM_TYPE_STREAMING) {
continue;
}
}
/* find units connected to this unit */
id = usb_ac_traverse_connections(uacp, unit, dir,
search_target, channel, control, all_or_one, count,
arg1, depth, func);
if ((all_or_one == USB_AC_FIND_ONE) &&
(id != USB_AC_ID_NONE)) {
unit = id;
break;
}
}
(*depth)--;
return ((unit < uacp->usb_ac_max_unit) ? unit : USB_AC_ID_NONE);
}
/*
* usb_ac_set_monitor_gain_control:
* search for a feature unit between output terminal (OT) and
* input terminal. We are looking for a path between
* for example a microphone and a speaker through a feature unit
* and mixer
*/
static uint_t
usb_ac_set_monitor_gain_control(usb_ac_state_t *uacp, uint_t dir,
uint_t search_target, uint_t channel, uint_t control,
uint_t all_or_one, uint_t *count, uint_t arg1,
int (*func)(usb_ac_state_t *uacp, uint_t unit, uint_t dir,
uint_t channel, uint_t control, uint_t arg1, uint_t *depth))
{
uint_t unit, id;
uint_t depth = 0;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_monitor_gain_control: dir=%d type=%d ch=%d cntl=%d",
dir, search_target, channel, control);
for (unit = 1; unit < uacp->usb_ac_max_unit; unit++) {
usb_audio_output_term_descr_t *d =
uacp->usb_ac_units[unit].acu_descriptor;
/* is this an OT and not stream type? */
if ((uacp->usb_ac_unit_type[unit] ==
USB_AUDIO_OUTPUT_TERMINAL) &&
(d->wTerminalType != USB_AUDIO_TERM_TYPE_STREAMING)) {
/* find units connected to this unit */
id = usb_ac_traverse_connections(uacp, unit, dir,
search_target, channel, control, all_or_one, count,
arg1, &depth, func);
if ((all_or_one == USB_AC_FIND_ONE) &&
(id != USB_AC_ID_NONE)) {
break;
}
}
}
ASSERT(depth == 0);
return (id);
}
/*
* usb_ac_push/pop_unit
* add/remove unit ID to the traverse path
*/
static void
usb_ac_push_unit_id(usb_ac_state_t *uacp, uint_t unit)
{
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_push_unit_id: pushing %d at %d", unit,
uacp->usb_ac_traverse_path_index);
uacp->usb_ac_traverse_path[uacp->usb_ac_traverse_path_index++] = unit;
ASSERT(uacp->usb_ac_traverse_path_index < uacp->usb_ac_max_unit);
}
static void
usb_ac_pop_unit_id(usb_ac_state_t *uacp, uint_t unit)
{
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_push_unit_id: popping %d at %d", unit,
uacp->usb_ac_traverse_path_index);
uacp->usb_ac_traverse_path[uacp->usb_ac_traverse_path_index--] = 0;
}
/*
* usb_ac_show_traverse_path:
* display entire path, just for debugging
*/
static void
usb_ac_show_traverse_path(usb_ac_state_t *uacp)
{
int i;
for (i = 0; i < uacp->usb_ac_traverse_path_index; i++) {
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"traverse path %d: unit=%d type=%d",
i, uacp->usb_ac_traverse_path[i],
uacp->usb_ac_unit_type[uacp->usb_ac_traverse_path[i]]);
}
}
/*
* usb_ac_check_path:
* check for a specified type in the traverse path
*/
static int
usb_ac_check_path(usb_ac_state_t *uacp, uint_t type)
{
int i;
for (i = 0; i < uacp->usb_ac_traverse_path_index; i++) {
uint_t unit = uacp->usb_ac_traverse_path[i];
if (uacp->usb_ac_unit_type[unit] == type) {
return (USB_SUCCESS);
}
}
return (USB_FAILURE);
}
/*
* usb_ac_traverse_connections:
* traverse all units and for each unit with the right type, call
* func. If the func returns a success and search == USB_AC_FIND_ONE,
* we are done. If all is set then we continue until we terminate
* and input or output terminal.
* For audio play, we traverse columns starting from an input terminal
* to an output terminal while for record we traverse rows from output
* terminal to input terminal.
*/
static uint_t
usb_ac_traverse_connections(usb_ac_state_t *uacp, uint_t start_unit, uint_t dir,
uint_t search_target, uint_t channel, uint_t control,
uint_t all_or_one, uint_t *count, uint_t arg1, uint_t *depth,
int (*func)(usb_ac_state_t *uacp, uint_t unit, uint_t dir,
uint_t channel, uint_t control, uint_t arg1, uint_t *depth))
{
uint_t unit, id;
uint_t done = (dir & AUDIO_PLAY) ? USB_AUDIO_OUTPUT_TERMINAL :
USB_AUDIO_INPUT_TERMINAL;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_traverse_connections: "
"start=%d dir=%d type=%d ch=%d cntl=%d all=%d depth=%d",
start_unit, dir, search_target, channel, control,
all_or_one, *depth);
/* keep track of recursion depth */
if ((*depth)++ > USB_AC_MAX_DEPTH) {
USB_DPRINTF_L1(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"Unit topology too complex, giving up");
return (USB_AC_ID_NONE);
}
usb_ac_push_unit_id(uacp, start_unit);
for (unit = 1; unit < uacp->usb_ac_max_unit; unit++) {
uint_t entry = (dir & AUDIO_PLAY) ?
uacp->usb_ac_connections[unit][start_unit] :
uacp->usb_ac_connections[start_unit][unit];
if (entry) {
USB_DPRINTF_L3(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"start=%d unit=%d entry=%d type=%d "
"done=%d found=%d",
start_unit, unit, entry, search_target, done,
uacp->usb_ac_unit_type[unit]);
/* did we find a matching type? */
if (uacp->usb_ac_unit_type[unit] == search_target) {
USB_DPRINTF_L3(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"match: dir=%d unit=%d type=%d",
dir, unit, search_target);
/* yes, no apply function to this unit */
if (func(uacp, unit, dir, channel,
control, arg1, depth) == USB_SUCCESS) {
(*count)++;
USB_DPRINTF_L3(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"func returned success, "
"unit=%d all=%d", unit,
all_or_one);
/* are we done? */
if (all_or_one == USB_AC_FIND_ONE) {
break;
}
}
}
/* did we find the terminating unit */
if (uacp->usb_ac_unit_type[unit] == done) {
continue;
}
id = usb_ac_traverse_connections(uacp, unit, dir,
search_target, channel, control,
all_or_one, count, arg1, depth, func);
if ((id != USB_AC_ID_NONE) &&
(all_or_one == USB_AC_FIND_ONE)) {
unit = id;
break;
}
}
}
(*depth)--;
usb_ac_pop_unit_id(uacp, start_unit);
return ((unit < uacp->usb_ac_max_unit) ? unit : USB_AC_ID_NONE);
}
/*
* Event Management
*
* usb_ac_disconnect_event_cb:
* The device has been disconnected. we either wait for
* detach or a reconnect event.
*/
static int
usb_ac_disconnect_event_cb(dev_info_t *dip)
{
usb_ac_state_t *uacp = (usb_ac_state_t *)ddi_get_soft_state(
usb_ac_statep, ddi_get_instance(dip));
USB_DPRINTF_L4(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle,
"usb_ac_disconnect_event_cb: dip=0x%p", (void *)dip);
usb_ac_serialize_access(uacp);
/* setting to disconnect state will prevent replumbing */
mutex_enter(&uacp->usb_ac_mutex);
uacp->usb_ac_dev_state = USB_DEV_DISCONNECTED;
if (uacp->usb_ac_busy_count) {
USB_DPRINTF_L0(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle,
"device was disconnected while busy. "
"Data may have been lost");
}
mutex_exit(&uacp->usb_ac_mutex);
USB_DPRINTF_L3(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle,
"usb_ac_disconnect_event_cb: done");
usb_ac_release_access(uacp);
return (USB_SUCCESS);
}
/*
* usb_ac_cpr_suspend:
*/
static int
usb_ac_cpr_suspend(dev_info_t *dip)
{
usb_ac_state_t *uacp = (usb_ac_state_t *)ddi_get_soft_state(
usb_ac_statep, ddi_get_instance(dip));
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_cpr_suspend: Begin");
mutex_enter(&uacp->usb_ac_mutex);
uacp->usb_ac_dev_state = USB_DEV_SUSPENDED;
mutex_exit(&uacp->usb_ac_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_cpr_suspend: End");
return (USB_SUCCESS);
}
/*
* usb_ac_reconnect_event_cb:
* The device was disconnected but this instance not detached, probably
* because the device was busy.
* if the same device, continue with restoring state
* We should either be in the unplumbed state or the plumbed open
* state.
*/
static int
usb_ac_reconnect_event_cb(dev_info_t *dip)
{
usb_ac_state_t *uacp = (usb_ac_state_t *)ddi_get_soft_state(
usb_ac_statep, ddi_get_instance(dip));
USB_DPRINTF_L4(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle,
"usb_ac_reconnect_event_cb: dip=0x%p", (void *)dip);
mutex_enter(&uacp->usb_ac_mutex);
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_serialize_access(uacp);
/* check the plumbing state */
mutex_enter(&uacp->usb_ac_mutex);
uacp->usb_ac_busy_count++;
if (uacp->usb_ac_plumbing_state ==
USB_AC_STATE_PLUMBED) {
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_restore_device_state(dip, uacp);
mutex_enter(&uacp->usb_ac_mutex);
}
uacp->usb_ac_busy_count--;
if (uacp->usb_ac_busy_count) {
USB_DPRINTF_L0(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle,
"busy device has been reconnected");
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
return (USB_SUCCESS);
}
/*
* usb_ac_cpr_resume:
* Restore device state
*/
static void
usb_ac_cpr_resume(dev_info_t *dip)
{
usb_ac_state_t *uacp = (usb_ac_state_t *)ddi_get_soft_state(
usb_ac_statep, ddi_get_instance(dip));
USB_DPRINTF_L4(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle,
"usb_ac_cpr_resume");
usb_ac_serialize_access(uacp);
usb_ac_restore_device_state(dip, uacp);
usb_ac_release_access(uacp);
}
/*
* usb_ac_restore_device_state:
* Set original configuration of the device
* enable wrq - this starts new transactions on the control pipe
*/
static void
usb_ac_restore_device_state(dev_info_t *dip, usb_ac_state_t *uacp)
{
usb_ac_power_t *uacpm;
int rval;
USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle,
"usb_ac_restore_device_state:");
usb_ac_pm_busy_component(uacp);
(void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
/* Check if we are talking to the same device */
if (usb_check_same_device(dip, uacp->usb_ac_log_handle,
USB_LOG_L0, PRINT_MASK_ALL,
USB_CHK_BASIC|USB_CHK_CFG, NULL) != USB_SUCCESS) {
usb_ac_pm_idle_component(uacp);
/* change the device state from suspended to disconnected */
mutex_enter(&uacp->usb_ac_mutex);
uacp->usb_ac_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&uacp->usb_ac_mutex);
return;
}
mutex_enter(&uacp->usb_ac_mutex);
uacpm = uacp->usb_ac_pm;
if (uacpm) {
if (uacpm->acpm_wakeup_enabled) {
mutex_exit(&uacp->usb_ac_mutex);
if ((rval = usb_handle_remote_wakeup(uacp->usb_ac_dip,
USB_REMOTE_WAKEUP_ENABLE)) != USB_SUCCESS) {
USB_DPRINTF_L4(PRINT_MASK_ATTA,
uacp->usb_ac_log_handle,
"usb_ac_restore_device_state: "
"remote wakeup "
"enable failed, rval=%d", rval);
}
mutex_enter(&uacp->usb_ac_mutex);
}
}
/* prevent unplumbing */
uacp->usb_ac_busy_count++;
uacp->usb_ac_dev_state = USB_DEV_ONLINE;
if (uacp->usb_ac_plumbing_state == USB_AC_STATE_PLUMBED) {
(void) usb_ac_restore_audio_state(uacp, 0);
}
uacp->usb_ac_busy_count--;
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_pm_idle_component(uacp);
}
/*
* usb_ac_am_restore_state
*/
static void
usb_ac_am_restore_state(void *arg)
{
usb_ac_state_t *uacp = (usb_ac_state_t *)arg;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_am_restore_state: Begin");
usb_ac_serialize_access(uacp);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_plumbing_state ==
USB_AC_STATE_PLUMBED_RESTORING) {
mutex_exit(&uacp->usb_ac_mutex);
/*
* allow hid and usb_as to restore themselves
* (some handshake would have been preferable though)
*/
delay(USB_AC_RESTORE_DELAY);
(void) audio_sup_restore_state(uacp->usb_ac_audiohdl,
AUDIO_ALL_DEVICES, AUDIO_BOTH);
mutex_enter(&uacp->usb_ac_mutex);
uacp->usb_ac_plumbing_state = USB_AC_STATE_PLUMBED;
}
/* allow unplumbing */
uacp->usb_ac_busy_count--;
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_am_restore_state: End");
}
/*
* usb_ac_restore_audio_state:
*/
static int
usb_ac_restore_audio_state(usb_ac_state_t *uacp, int flag)
{
ASSERT(mutex_owned(&uacp->usb_ac_mutex));
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_restore_audio_state: flag=%d", flag);
switch (uacp->usb_ac_plumbing_state) {
case USB_AC_STATE_PLUMBED:
uacp->usb_ac_plumbing_state =
USB_AC_STATE_PLUMBED_RESTORING;
break;
case USB_AC_STATE_UNPLUMBED:
return (USB_SUCCESS);
case USB_AC_STATE_PLUMBED_RESTORING:
default:
return (USB_FAILURE);
}
/*
* increment busy_count again, it will be decremented
* in usb_ac_am_restore_state
*/
uacp->usb_ac_busy_count++;
if (flag & USB_FLAGS_SLEEP) {
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_am_restore_state((void *)uacp);
mutex_enter(&uacp->usb_ac_mutex);
} else {
mutex_exit(&uacp->usb_ac_mutex);
if (usb_async_req(uacp->usb_ac_dip,
usb_ac_am_restore_state,
(void *)uacp, USB_FLAGS_SLEEP) != USB_SUCCESS) {
mutex_enter(&uacp->usb_ac_mutex);
uacp->usb_ac_busy_count--;
return (USB_FAILURE);
}
mutex_enter(&uacp->usb_ac_mutex);
}
return (USB_SUCCESS);
}
/*
* Mixer Callback Management
* NOTE: all mixer callbacks are serialized. we cannot be closed while
* we are in the middle of a callback. There needs to be a
* teardown first. We cannot be unplumbed as long as we are
* still open.
*
* usb_ac_setup:
* Send setup to usb_as if the first setup
* Check power is done in usb_ac_send_as_cmd()
*/
static int
usb_ac_setup(audiohdl_t ahdl, int stream, int flag)
{
int rval = AUDIO_SUCCESS;
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_setup: Begin ahdl=0x%p, stream=%d, flag=%d",
(void *)ahdl, stream, flag);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) {
mutex_exit(&uacp->usb_ac_mutex);
return (AUDIO_FAILURE);
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_serialize_access(uacp);
if (flag & AUDIO_PLAY) {
rval = usb_ac_do_setup(ahdl, stream, AUDIO_PLAY);
}
if ((rval == USB_SUCCESS) && (flag & AUDIO_RECORD)) {
rval = usb_ac_do_setup(ahdl, stream, AUDIO_RECORD);
}
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_setup: rval=%d", rval);
return ((rval == USB_SUCCESS) ? AUDIO_SUCCESS : AUDIO_FAILURE);
}
/*
* usb_ac_do_setup:
* Wrapper function for usb_ac_setup which can be called
* either from audio framework for usb_ac_set_format
*/
static int
usb_ac_do_setup(audiohdl_t ahdl, int stream, int flag)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_ac_plumbed_t *plumb_infop = NULL;
usb_ac_streams_info_t *streams_infop = NULL;
int dir;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_setup: Begin ahdl=0x%p, stream=%d, flag=%d",
(void *)ahdl, stream, flag);
mutex_enter(&uacp->usb_ac_mutex);
dir = (flag & AUDIO_PLAY) ? AUDIO_PLAY : AUDIO_RECORD;
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", dir);
ASSERT(plumb_infop != NULL);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
/*
* Handle multiple setup calls. Pass the setup call to usb_as only
* the first time so isoc pipe will be opened only once
*/
if (streams_infop->acs_setup_teardown_count++) {
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_setup: more than one setup, cnt=%d",
streams_infop->acs_setup_teardown_count);
mutex_exit(&uacp->usb_ac_mutex);
return (USB_SUCCESS);
}
/* Send setup command to usb_as */
if (usb_ac_send_as_cmd(uacp, plumb_infop, USB_AUDIO_SETUP, 0) !=
USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_setup: failure");
streams_infop->acs_setup_teardown_count--;
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
return (USB_FAILURE);
}
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_setup: End");
return (USB_SUCCESS);
}
/*
* usb_ac_teardown:
* Send teardown to usb_as if the last teardown
* Check power is done in usb_ac_send_as_cmd()
* NOTE: allow teardown when disconnected
*/
static void
usb_ac_teardown(audiohdl_t ahdl, int stream, int flag)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_teardown: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
usb_ac_serialize_access(uacp);
if (flag & AUDIO_PLAY) {
usb_ac_do_teardown(ahdl, stream, AUDIO_PLAY);
}
if (flag & AUDIO_RECORD) {
usb_ac_do_teardown(ahdl, stream, AUDIO_RECORD);
}
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_teardown: End");
}
/*
* usb_ac_do_teardown()
* Check power is done in usb_ac_send_as_cmd()
*/
static void
usb_ac_do_teardown(audiohdl_t ahdl, int stream, int flag)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_ac_plumbed_t *plumb_infop = NULL;
usb_ac_streams_info_t *streams_infop = NULL;
int dir;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_teardown: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
mutex_enter(&uacp->usb_ac_mutex);
dir = (flag & AUDIO_PLAY) ? AUDIO_PLAY : AUDIO_RECORD;
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", dir);
ASSERT(plumb_infop != NULL);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
/* There should be at least one matching setup call */
ASSERT(streams_infop->acs_setup_teardown_count);
/*
* Handle multiple setup/teardown calls. Pass the call to usb_as
* only this is the last teardown so that isoc pipe is closed
* only once
*/
if (--(streams_infop->acs_setup_teardown_count)) {
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_teardown: more than one setup/teardown, "
"cnt=%d",
streams_infop->acs_setup_teardown_count);
mutex_exit(&uacp->usb_ac_mutex);
return;
}
/* Send teardown command to usb_as */
if (usb_ac_send_as_cmd(uacp, plumb_infop, USB_AUDIO_TEARDOWN,
(void *)NULL) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_teardown: failure");
streams_infop->acs_setup_teardown_count++;
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
return;
}
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_teardown: End");
}
/*
* usb_ac_set_config:
* This routine will send control commands to get the max
* and min gain balance, calculate the gain to be set from the
* arguments and send another control command to set it.
* Check power is done here since we will access the default pipe
*/
static int
usb_ac_set_config(audiohdl_t ahdl, int stream, int command, int flag,
int arg1, int arg2)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
char *what;
int rval = AUDIO_FAILURE;
uint_t channel;
uchar_t n_channels = 0;
uint_t dir, count;
short muteval;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_config: Begin ahdl=0x%p\n\t"
"stream=%d, cmd=%d, flag=%d, arg1=%d, arg2=%d",
(void *)ahdl, stream, command, flag, arg1, arg2);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_plumbing_state < USB_AC_STATE_PLUMBED) {
mutex_exit(&uacp->usb_ac_mutex);
return (AUDIO_FAILURE);
}
if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) {
mutex_exit(&uacp->usb_ac_mutex);
return (AUDIO_FAILURE);
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_serialize_access(uacp);
mutex_enter(&uacp->usb_ac_mutex);
switch (command) {
case AM_SET_GAIN:
/*
* Set the gain for a channel. The audio mixer calculates the
* impact, if any, on the channel's gain.
*
* 0 <= gain <= AUDIO_MAX_GAIN
*
* arg1 --> gain
* arg2 --> channel #, 0 == left, 1 == right
*/
what = "gain";
channel = ++arg2;
ASSERT(flag != AUDIO_BOTH);
dir = (flag & AUDIO_PLAY) ? AUDIO_PLAY : AUDIO_RECORD;
/*
* We service the set_config command when the device is
* plumbed and opened.
*/
n_channels = usb_ac_get_curr_n_channels(uacp, dir);
if (channel > n_channels) {
USB_DPRINTF_L2(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"usb_ac_set_config: channel(%d) passed is "
" > n_channels(%d)", channel, n_channels);
goto done;
}
count = 0;
(void) usb_ac_set_control(uacp, dir,
USB_AUDIO_FEATURE_UNIT, channel,
USB_AUDIO_VOLUME_CONTROL,
USB_AC_FIND_ALL, &count, arg1, usb_ac_set_gain);
/*
* If feature unit id could not be found, it probably means
* volume/gain control is not available for this device.
* and we just return success if we haven't completed
* the registration with the mixer yet
*/
if (count == 0) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"mixer=%d, no featureID, arg1=%d",
uacp->usb_ac_registered_with_mixer, arg1);
rval = (uacp->usb_ac_registered_with_mixer == 0) ?
AUDIO_SUCCESS : AUDIO_FAILURE;
} else {
rval = AUDIO_SUCCESS;
}
break;
case AM_SET_PORT:
what = "port";
ASSERT(flag != AUDIO_BOTH);
dir = (flag & AUDIO_PLAY) ? AUDIO_PLAY : AUDIO_RECORD;
rval = usb_ac_set_port(uacp, dir, arg1);
rval = (rval == USB_SUCCESS) ? AUDIO_SUCCESS : AUDIO_FAILURE;
break;
case AM_SET_MONITOR_GAIN:
what = "monitor gain";
channel = ++arg2;
dir = AUDIO_RECORD;
/*
* We service the set_config command when the device is
* plumbed and opened.
*/
n_channels = usb_ac_get_curr_n_channels(uacp, dir);
if (channel > n_channels) {
USB_DPRINTF_L2(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"usb_ac_set_config: channel(%d) passed is "
" > n_channels(%d)", channel, n_channels);
goto done;
}
count = 0;
(void) usb_ac_set_monitor_gain_control(uacp, dir,
USB_AUDIO_INPUT_TERMINAL, channel,
USB_AUDIO_VOLUME_CONTROL,
USB_AC_FIND_ALL, &count, arg1,
usb_ac_set_monitor_gain);
/*
* always return success since we told the mixer
* we always support this and sdtaudiocontrol displays
* monitor gain regardless.
*/
rval = AUDIO_SUCCESS;
break;
case AM_OUTPUT_MUTE:
what = "mute";
ASSERT(flag != AUDIO_BOTH);
dir = (flag & AUDIO_PLAY) ? AUDIO_PLAY : AUDIO_RECORD;
/*
* arg1 != 0 --> mute
* arg1 == 0 --> unmute
* arg2 --> not used
*/
muteval = (arg1 == 0) ? USB_AUDIO_MUTE_OFF :
USB_AUDIO_MUTE_ON;
count = 0;
(void) usb_ac_set_control(uacp, dir,
USB_AUDIO_FEATURE_UNIT, 0,
USB_AUDIO_MUTE_CONTROL,
USB_AC_FIND_ALL, &count, muteval,
usb_ac_set_mute);
rval = (count == 0) ? AUDIO_FAILURE : AUDIO_SUCCESS;
break;
case AM_MIC_BOOST:
what = "mic boost";
rval = AUDIO_SUCCESS;
break;
case AM_SET_GAIN_BAL:
what = "set gain bal";
rval = AUDIO_FAILURE;
break;
default:
what = "unknown";
rval = AUDIO_FAILURE;
}
done:
mutex_exit(&uacp->usb_ac_mutex);
/* Now it's safe to release access to other routines */
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_config: %s done, rval=%d", what, rval);
return (rval);
}
/*
* usb_ac_set_monitor_gain:
* called for each output terminal which supports
* from usb_ac_traverse_connections
*/
static int
usb_ac_set_monitor_gain(usb_ac_state_t *uacp, uint_t unit,
uint_t dir, uint_t channel, uint_t control, uint_t gain, uint_t *depth)
{
usb_audio_output_term_descr_t *d =
uacp->usb_ac_units[unit].acu_descriptor;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_monitor_gain: ");
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"id=%d dir=%d ch=%d cntl=%d gain=%d type=%d term type=0x%x",
unit, dir, channel, control, gain,
uacp->usb_ac_unit_type[unit], d->wTerminalType);
/* log how we got here */
usb_ac_push_unit_id(uacp, unit);
usb_ac_show_traverse_path(uacp);
usb_ac_pop_unit_id(uacp, unit);
/* we only care about the ITs connected to real hw inputs */
switch (d->wTerminalType) {
case USB_AUDIO_TERM_TYPE_STREAMING:
return (USB_FAILURE);
case USB_AUDIO_TERM_TYPE_DT_MICROPHONE:
case USB_AUDIO_TERM_TYPE_PERS_MICROPHONE:
case USB_AUDIO_TERM_TYPE_OMNI_DIR_MICROPHONE:
case USB_AUDIO_TERM_TYPE_MICROPHONE_ARRAY:
case USB_AUDIO_TERM_TYPE_PROCESSING_MIC_ARRAY:
default:
break;
}
/*
* we can only do this if the microphone is mixed into the
* audio output so look for a mixer first
*/
if (usb_ac_check_path(uacp, USB_AUDIO_MIXER_UNIT) ==
USB_SUCCESS) {
int i, id;
/* now look for a feature unit */
for (i = uacp->usb_ac_traverse_path_index - 1; i >= 0;
i--) {
id = uacp->usb_ac_traverse_path[i];
switch (uacp->usb_ac_unit_type[id]) {
case USB_AUDIO_MIXER_UNIT:
/* the FU should be before the mixer */
return (USB_FAILURE);
case USB_AUDIO_FEATURE_UNIT:
/*
* now set the volume
*/
if (usb_ac_set_gain(uacp, id, dir, channel,
control, gain, depth) != USB_SUCCESS) {
/* try master channel */
if (usb_ac_set_gain(uacp, id, dir,
0, control, gain, depth) !=
USB_SUCCESS) {
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
default:
continue;
}
}
}
return (USB_FAILURE);
}
/*
* usb_ac_set_gain is called for each feature unit which supports
* the requested controls from usb_ac_traverse_connections
* we still need to check whether this unit supports the requested
* control.
*/
static int
usb_ac_set_gain(usb_ac_state_t *uacp, uint_t featureID,
uint_t dir, uint_t channel, uint_t control, uint_t gain, uint_t *depth)
{
short max, min, current;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: id=%d dir=%d ch=%d cntl=%d gain=%d",
featureID, dir, channel, control, gain);
if (usb_ac_feature_unit_check(uacp, featureID,
dir, channel, control, gain, depth) != USB_SUCCESS) {
return (USB_FAILURE);
}
if ((max = usb_ac_get_maxmin_volume(uacp, channel,
USB_AUDIO_GET_MAX, dir, featureID)) == USB_FAILURE) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: getting max gain failed");
return (USB_FAILURE);
}
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: channel %d, max=%d", channel, max);
if ((min = usb_ac_get_maxmin_volume(uacp, channel,
USB_AUDIO_GET_MIN, dir, featureID)) == USB_FAILURE) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: getting min gain failed");
return (USB_FAILURE);
}
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: channel=%d, min=%d", channel, min);
if ((current = usb_ac_get_maxmin_volume(uacp, channel,
USB_AUDIO_GET_CUR, dir, featureID)) == USB_FAILURE) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: getting cur gain failed");
return (USB_FAILURE);
}
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: channel=%d, cur=%d", channel, current);
/*
* Set the gain for a channel. The audio mixer calculates the
* impact, if any, on the channel's gain.
*
* 0 <= gain <= AUDIO_MAX_GAIN
*
* channel #, 0 == left, 1 == right
*/
if (gain == 0) {
gain = USB_AUDIO_VOLUME_SILENCE;
} else {
gain = max - ((max - min) * (0x100 - gain))/0x100;
}
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: ch=%d dir=%d max=%d min=%d gain=%d",
channel, dir, max, min, gain);
if (usb_ac_set_volume(uacp, channel, gain, dir,
featureID) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: setting volume failed");
return (USB_FAILURE);
}
/* just curious, read it back, device may round up/down */
if ((current = usb_ac_get_maxmin_volume(uacp, channel,
USB_AUDIO_GET_CUR, dir, featureID)) == USB_FAILURE) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain: getting cur gain failed");
}
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_gain done: "
"id=%d channel=%d, cur=%d gain=%d", featureID, channel,
(ushort_t)current, (ushort_t)gain);
return (USB_SUCCESS);
}
/*
* usb_ac_set_format
* This mixer callback initiates a command to be sent to
* usb_as to select an alternate with the passed characteristics
* and also to set the sample frequency.
* Note that this may be called when a playing is going on in
* the streaming interface. To handle that, first stop
* playing/recording, close the pipe by sending a teardown
* command, send the set_format command down and then reopen
* the pipe. Note : (1) audio framework will restart play/record
* after a set_format command. (2) Check power is done in
* usb_ac_send_as_cmd().
*/
static int
usb_ac_set_format(audiohdl_t ahdl, int stream, int flag,
int sample, int channels, int precision, int encoding)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_audio_formats_t *format;
usb_audio_formats_t old_format;
usb_ac_plumbed_t *plumb_infop;
usb_ac_streams_info_t *streams_infop = NULL;
int old_setup_teardown_count;
int dir;
int rval;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_format: Begin ahdl=0x%p, stream=%d, flag=%d, "
"sr=%d, chnls=%d, prec=%d, enc=%d", (void *)ahdl, stream, flag,
sample, channels, precision, encoding);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) {
mutex_exit(&uacp->usb_ac_mutex);
return (AUDIO_FAILURE);
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_serialize_access(uacp);
ASSERT(flag != AUDIO_BOTH);
mutex_enter(&uacp->usb_ac_mutex);
dir = (flag & AUDIO_PLAY) ? AUDIO_PLAY : AUDIO_RECORD;
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", dir);
if (plumb_infop == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_format: no plumb info");
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
return (AUDIO_FAILURE);
}
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
/* isoc pipe not open and playing is not in progress */
if (streams_infop->acs_setup_teardown_count == 0) {
mutex_exit(&uacp->usb_ac_mutex);
rval = usb_ac_send_format_cmd(ahdl, stream, dir, sample,
channels, precision, encoding);
usb_ac_release_access(uacp);
return ((rval == USB_SUCCESS) ?
AUDIO_SUCCESS : AUDIO_FAILURE);
}
/* isoc pipe is open and playing might be in progress */
format = &streams_infop->acs_ac_to_as_req.acr_curr_format;
/* Keep a copy of the old format */
bcopy((void *)format, (void *)&old_format,
sizeof (usb_audio_formats_t));
ASSERT(streams_infop->acs_setup_teardown_count != 0);
old_setup_teardown_count = streams_infop->acs_setup_teardown_count;
streams_infop->acs_setup_teardown_count = 1;
mutex_exit(&uacp->usb_ac_mutex);
if (dir == AUDIO_PLAY) {
usb_ac_do_pause_play(ahdl, stream);
} else if (dir == AUDIO_RECORD) {
usb_ac_do_stop_record(ahdl, stream);
}
/* This blocks until the current isoc xfer is over */
usb_ac_do_teardown(ahdl, stream, dir);
if (usb_ac_send_format_cmd(ahdl, stream, dir, sample,
channels, precision, encoding) != USB_SUCCESS) {
/*
* Setting new alternate has failed, try restoring
* old one.
* If there is a bandwidth failure, hang around
* till bandwidth is available. Also we know that
* there is a matching alternate, so that can't fail.
*/
if (usb_ac_send_format_cmd(ahdl, stream, dir,
old_format.fmt_sr, old_format.fmt_chns,
old_format.fmt_precision, old_format.fmt_encoding) ==
USB_FAILURE) {
/* We closed the pipe; reopen it */
(void) usb_ac_do_setup(ahdl, stream, dir);
mutex_enter(&uacp->usb_ac_mutex);
streams_infop->acs_setup_teardown_count =
old_setup_teardown_count;
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
return (AUDIO_FAILURE);
}
}
/* This should block until successful */
(void) usb_ac_do_setup(ahdl, stream, dir);
mutex_enter(&uacp->usb_ac_mutex);
streams_infop->acs_setup_teardown_count = old_setup_teardown_count;
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_format: End");
return (AUDIO_SUCCESS);
}
/*
* usb_ac_get_curr_n_channels:
* Return no. of channels from the current format table
*/
static int
usb_ac_get_curr_n_channels(usb_ac_state_t *uacp, int dir)
{
usb_audio_formats_t *cur_fmt = usb_ac_get_curr_format(uacp, dir);
return (cur_fmt->fmt_chns);
}
/*
* usb_ac_get_cur_format:
* Get format for the current alternate
*/
static usb_audio_formats_t *
usb_ac_get_curr_format(usb_ac_state_t *uacp, int dir)
{
usb_ac_plumbed_t *plumb_infop;
usb_ac_streams_info_t *streams_infop = NULL;
ASSERT(mutex_owned(&uacp->usb_ac_mutex));
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", dir);
if (plumb_infop == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_get_curr_format: no plumb info");
return (NULL);
}
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
return (&streams_infop->acs_cur_fmt);
}
/*
* usb_ac_send_format_cmd
* Sets format and get alternate setting that matches with
* the format from the usb_as playing or recording interface
* Send the set sample freq command down to usb_as.
*/
static int
usb_ac_send_format_cmd(audiohdl_t ahdl, int stream, int dir,
int sample, int channels, int precision, int encoding)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_audio_formats_t *format;
usb_ac_plumbed_t *plumb_infop = NULL;
usb_ac_streams_info_t *streams_infop = NULL;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_format_cmd: Begin ahdl=0x%p, stream=%d, dir=%d, "
"sr=%d, chnls=%d, prec=%d, enc=%d", (void *)ahdl, stream, dir,
sample, channels, precision, encoding);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) {
mutex_exit(&uacp->usb_ac_mutex);
return (USB_FAILURE);
}
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", dir);
ASSERT(plumb_infop);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
ASSERT(dir == AUDIO_PLAY || dir == AUDIO_RECORD);
streams_infop->acs_ac_to_as_req.acr_curr_dir = dir;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_format_cmd: plumb_infop=0x%p, streams_infop=0x%p",
(void *)plumb_infop, (void *)streams_infop);
format = &(streams_infop->acs_ac_to_as_req.acr_curr_format);
bzero(format, sizeof (usb_audio_formats_t));
/* save format info */
format->fmt_sr = (uint_t)sample;
format->fmt_chns = (uchar_t)channels;
format->fmt_precision = (uchar_t)precision;
format->fmt_encoding = (uchar_t)encoding;
streams_infop->acs_cur_fmt = *format;
/*
* Set format for the streaming interface with lower write queue
* This boils down to set_alternate interface command in
* usb_as and the reply mp contains the currently active
* alternate number that is stored in the as_req structure
*/
if (usb_ac_send_as_cmd(uacp, plumb_infop,
USB_AUDIO_SET_FORMAT, format) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"usb_ac_send_format_cmd: failed");
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
return (USB_FAILURE);
} else {
/* alternate number stored and reply mp freed */
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
}
/* Set the sample rate */
if (usb_ac_send_as_cmd(uacp, plumb_infop, USB_AUDIO_SET_SAMPLE_FREQ,
&sample) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_format_cmd: setting format failed");
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
return (USB_FAILURE);
}
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_format_cmd: End");
return (USB_SUCCESS);
}
/*
* usb_ac_start_play
* Send a start_play command down to usb_as
* Check power is done in usb_ac_send_as_cmd()
*/
static int
usb_ac_start_play(audiohdl_t ahdl, int stream)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_audio_formats_t *cur_fmt;
usb_ac_plumbed_t *plumb_infop = NULL;
int dir, samples;
usb_audio_play_req_t play_req;
usb_ac_streams_info_t *streams_infop = NULL;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_start_play: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) {
mutex_exit(&uacp->usb_ac_mutex);
return (AUDIO_FAILURE);
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_serialize_access(uacp);
mutex_enter(&uacp->usb_ac_mutex);
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", AUDIO_PLAY);
ASSERT(plumb_infop);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_start_play: plumb_infop=0x%p, streams_infop=0x%p",
(void *)plumb_infop, (void *)streams_infop);
dir = streams_infop->acs_ac_to_as_req.acr_curr_dir;
ASSERT(dir == AUDIO_PLAY);
cur_fmt = &streams_infop->acs_ac_to_as_req.acr_curr_format;
/* Check for continuous sample rate done in usb_as */
samples = cur_fmt->fmt_sr * cur_fmt->fmt_chns /
uacp->usb_ac_am_ad_info.ad_play.ad_int_rate;
if (samples & cur_fmt->fmt_chns) {
samples++;
}
play_req.up_samples = samples;
play_req.up_handle = ahdl;
/* Send setup command to usb_as */
if (usb_ac_send_as_cmd(uacp, plumb_infop, USB_AUDIO_START_PLAY,
(void *)&play_req) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_start_play: failure");
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
return (AUDIO_FAILURE);
}
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_start_play: End");
return (AUDIO_SUCCESS);
}
/*
* usb_ac_pause_play:
* Wrapper function for usb_ac_do_pause_play and gets
* called from mixer framework.
*/
static void
usb_ac_pause_play(audiohdl_t ahdl, int stream)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_pause_play: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) {
mutex_exit(&uacp->usb_ac_mutex);
return;
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_serialize_access(uacp);
usb_ac_do_pause_play(ahdl, stream);
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_pause_play: End");
}
/*
* usb_ac_do_pause_play:
* Send a pause_play command to usb_as.
* Check power is done in usb_ac_send_as_cmd()
*/
static void
usb_ac_do_pause_play(audiohdl_t ahdl, int stream)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_ac_plumbed_t *plumb_infop = NULL;
usb_ac_streams_info_t *streams_infop = NULL;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_pause_play: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
mutex_enter(&uacp->usb_ac_mutex);
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", AUDIO_PLAY);
ASSERT(plumb_infop);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
/* Send setup command to usb_as */
if (usb_ac_send_as_cmd(uacp, plumb_infop, USB_AUDIO_PAUSE_PLAY,
(void *)NULL) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_pause_play: failure");
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
}
mutex_exit(&uacp->usb_ac_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_pause_play: End");
}
/*
* usb_ac_stop_play:
* Wrapper function for usb_ac_pause_play and gets
* called from mixer framework.
*/
static void
usb_ac_stop_play(audiohdl_t ahdl, int stream)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_stop_play: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
usb_ac_pause_play(ahdl, stream);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_stop_play: End");
}
/*
* usb_ac_start_record:
* Sends a start record command down to usb_as.
* Check power is done in usb_ac_send_as_cmd()
*/
static int
usb_ac_start_record(audiohdl_t ahdl, int stream)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_ac_plumbed_t *plumb_infop = NULL;
usb_ac_streams_info_t *streams_infop = NULL;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_start_record: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
mutex_enter(&uacp->usb_ac_mutex);
if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) {
mutex_exit(&uacp->usb_ac_mutex);
return (AUDIO_FAILURE);
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_serialize_access(uacp);
mutex_enter(&uacp->usb_ac_mutex);
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", AUDIO_RECORD);
ASSERT(plumb_infop);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
/* Send setup command to usb_as */
if (usb_ac_send_as_cmd(uacp, plumb_infop, USB_AUDIO_START_RECORD,
(void *)&ahdl) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_start_record: failure");
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
return (AUDIO_FAILURE);
}
mutex_exit(&uacp->usb_ac_mutex);
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_start_record: End");
return (AUDIO_SUCCESS);
}
/*
* usb_ac_stop_record:
* Wrapper function for usb_ac_do_stop_record and is
* called form mixer framework.
*/
static void
usb_ac_stop_record(audiohdl_t ahdl, int stream)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_stop_record: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
usb_ac_serialize_access(uacp);
usb_ac_do_stop_record(ahdl, stream);
usb_ac_release_access(uacp);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_stop_record: End");
}
/*
* usb_ac_do_stop_record:
* Sends a stop_record command down.
* Check power is done in usb_ac_send_as_cmd()
*/
static void
usb_ac_do_stop_record(audiohdl_t ahdl, int stream)
{
usb_ac_state_t *uacp = audio_sup_get_private(ahdl);
usb_ac_plumbed_t *plumb_infop = NULL;
usb_ac_streams_info_t *streams_infop = NULL;
ASSERT(uacp != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_stop_record: Begin ahdl=0x%p, stream=%d",
(void *)ahdl, stream);
mutex_enter(&uacp->usb_ac_mutex);
plumb_infop = usb_ac_get_plumb_info(uacp, "usb_as", AUDIO_RECORD);
ASSERT(plumb_infop != NULL);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
/* Send setup command to usb_as */
if (usb_ac_send_as_cmd(uacp, plumb_infop, USB_AUDIO_STOP_RECORD,
NULL) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_stop_record: failure");
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
}
mutex_exit(&uacp->usb_ac_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_do_stop_record: End");
}
/*
* Helper Functions for Mixer callbacks
*
* usb_ac_get_maxmin_volume:
* Send USBA command down to get the maximum or minimum gain balance
* Calculate min or max gain balance and return that. Return
* USB_FAILURE for failure cases
*/
static int
usb_ac_get_maxmin_volume(usb_ac_state_t *uacp, uint_t channel, int cmd,
int dir, int feature_unitID)
{
mblk_t *data = NULL;
short max_or_min;
usb_cr_t cr;
usb_cb_flags_t cb_flags;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_get_maxmin_volume: channel=%d, cmd=%d dir=%d",
channel, cmd, dir);
mutex_exit(&uacp->usb_ac_mutex);
if (usb_pipe_sync_ctrl_xfer(
uacp->usb_ac_dip,
uacp->usb_ac_default_ph,
USB_DEV_REQ_DEV_TO_HOST |
USB_DEV_REQ_TYPE_CLASS |
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
cmd, /* bRequest */
(USB_AUDIO_VOLUME_CONTROL << 8) | channel, /* wValue */
/* feature unit and id */
(feature_unitID << 8)| uacp->usb_ac_ifno, /* wIndex */
2, /* wLength */
&data,
USB_ATTRS_NONE,
&cr, &cb_flags,
USB_FLAGS_SLEEP) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_get_maxmin_volume: failed, "
"cr=%d, cb=0x%x cmd=%d, data=0x%p",
cr, cb_flags, cmd, (void *)data);
freemsg(data);
mutex_enter(&uacp->usb_ac_mutex);
return (USB_FAILURE);
}
mutex_enter(&uacp->usb_ac_mutex);
ASSERT((data->b_wptr - data->b_rptr) == 2);
max_or_min = (*(data->b_rptr+1) << 8) | *data->b_rptr;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_get_maxmin_volume: max_or_min=0x%x", max_or_min);
freemsg(data);
return (max_or_min);
}
/*
* usb_ac_set_volume:
* Send USBA command down to set the gain balance
*/
static int
usb_ac_set_volume(usb_ac_state_t *uacp, uint_t channel, short gain, int dir,
int feature_unitID)
{
mblk_t *data = NULL;
usb_cr_t cr;
usb_cb_flags_t cb_flags;
int rval = USB_FAILURE;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_volume: channel=%d gain=%d dir=%d FU=%d",
channel, gain, dir, feature_unitID);
mutex_exit(&uacp->usb_ac_mutex);
/* Construct the mblk_t from gain for sending to USBA */
data = allocb_wait(4, BPRI_HI, STR_NOSIG, NULL);
*(data->b_wptr++) = (char)gain;
*(data->b_wptr++) = (char)(gain >> 8);
if ((rval = usb_pipe_sync_ctrl_xfer(
uacp->usb_ac_dip,
uacp->usb_ac_default_ph,
USB_DEV_REQ_HOST_TO_DEV |
USB_DEV_REQ_TYPE_CLASS |
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
USB_AUDIO_SET_CUR, /* bRequest */
(USB_AUDIO_VOLUME_CONTROL << 8) | channel, /* wValue */
/* feature unit and id */
(feature_unitID << 8) | uacp->usb_ac_ifno, /* wIndex */
2, /* wLength */
&data, 0,
&cr, &cb_flags, USB_FLAGS_SLEEP)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_volume: failed, cr=%d cb=0x%x",
cr, cb_flags);
}
freemsg(data);
mutex_enter(&uacp->usb_ac_mutex);
return (rval);
}
/*
* usb_ac_set_mute is called for each unit that supports the
* requested control from usb_ac_traverse_connections
*/
static int
usb_ac_set_mute(usb_ac_state_t *uacp, uint_t featureID, uint_t dir,
uint_t channel, uint_t control, uint_t muteval, uint_t *depth)
{
mblk_t *data;
usb_cr_t cr;
usb_cb_flags_t cb_flags;
int rval = USB_FAILURE;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_mute: muteval=0x%x, dir=%d", muteval, dir);
if (usb_ac_feature_unit_check(uacp, featureID,
dir, channel, control, 0, depth) != USB_SUCCESS) {
return (USB_FAILURE);
}
mutex_exit(&uacp->usb_ac_mutex);
/* Construct the mblk_t for sending to USBA */
data = allocb_wait(1, BPRI_HI, STR_NOSIG, NULL);
*(data->b_wptr++) = (char)muteval;
if ((rval = usb_pipe_sync_ctrl_xfer(
uacp->usb_ac_dip,
uacp->usb_ac_default_ph,
USB_DEV_REQ_HOST_TO_DEV |
USB_DEV_REQ_TYPE_CLASS |
USB_DEV_REQ_RCPT_IF, /* bmRequestType */
USB_AUDIO_SET_CUR, /* bRequest */
(USB_AUDIO_MUTE_CONTROL << 8) | channel, /* wValue */
/* feature unit and id */
(featureID << 8) | uacp->usb_ac_ifno, /* wIndex */
1, /* wLength */
&data,
0, /* attributes */
&cr, &cb_flags, 0)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_set_mute: failed, cr=%d cb=0x%x", cr, cb_flags);
}
freemsg(data);
mutex_enter(&uacp->usb_ac_mutex);
return (rval);
}
/*
* usb_ac_send_as_cmd:
* Allocate message blk, send a command down to usb_as,
* wait for the reply and free the message
*
* although not really needed to raise power if sending to as
* it seems better to ensure that both interfaces are at full power
*/
static int
usb_ac_send_as_cmd(usb_ac_state_t *uacp, usb_ac_plumbed_t *plumb_infop,
int cmd, void *arg)
{
mblk_t *mp = NULL;
struct iocblk *iocp;
queue_t *lwq = plumb_infop->acp_lwq;
usb_ac_streams_info_t *streams_infop;
int error = USB_FAILURE;
ASSERT(mutex_owned(&uacp->usb_ac_mutex));
ASSERT(plumb_infop != NULL);
streams_infop = (usb_ac_streams_info_t *)plumb_infop->acp_data;
ASSERT(streams_infop != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_as_cmd: Begin lwq=0x%p, cmd=0x%x, arg=0x%p",
(void *)lwq, cmd, arg);
if (!canputnext(lwq)) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_as_cmd: canputnext failed");
return (error);
}
/*
* Allocate mblk for a particular command
*/
switch (cmd) {
case USB_AUDIO_SET_FORMAT:
mp = usb_ac_allocate_req_mblk(uacp, cmd, (void *)arg,
sizeof (usb_audio_formats_t));
break;
case USB_AUDIO_TEARDOWN:
case USB_AUDIO_STOP_RECORD:
case USB_AUDIO_PAUSE_PLAY:
case USB_AUDIO_SETUP:
mp = usb_ac_allocate_req_mblk(uacp, cmd, NULL, 0);
break;
case USB_AUDIO_START_RECORD:
mp = usb_ac_allocate_req_mblk(uacp, cmd, (void *)arg,
sizeof (audiohdl_t *));
break;
case USB_AUDIO_SET_SAMPLE_FREQ:
mp = usb_ac_allocate_req_mblk(uacp, cmd, (void *)arg,
sizeof (int));
break;
case USB_AUDIO_START_PLAY:
mp = usb_ac_allocate_req_mblk(uacp, cmd, (void *)arg,
sizeof (usb_audio_play_req_t));
break;
default:
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_as_cmd: unknown cmd=%d", cmd);
return (error);
}
if (mp == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_as_cmd: can't get mblk to send cmd down");
return (error);
}
/*
* Set wait flag and send message down; we have made sure
* before that canputnext succeeds. Note mp will be freed down
*/
streams_infop->acs_ac_to_as_req.acr_wait_flag = 1;
mutex_exit(&uacp->usb_ac_mutex);
putnext(lwq, mp);
mutex_enter(&uacp->usb_ac_mutex);
/*
* Wait for the response; reply will arrive through rput()
* M_CTL and the cv_wait will be signaled there and wait flag
* will be reset
*/
while (streams_infop->acs_ac_to_as_req.acr_wait_flag) {
#ifndef DEBUG
cv_wait(&streams_infop->acs_ac_to_as_req.acr_cv,
&uacp->usb_ac_mutex);
#else
clock_t tm = ddi_get_lbolt() +
drv_usectohz(usb_ac_wait_timeout);
int rval;
rval = cv_timedwait(&streams_infop->acs_ac_to_as_req.acr_cv,
&uacp->usb_ac_mutex, tm);
if (streams_infop->acs_ac_to_as_req.acr_wait_flag) {
if (rval == -1) {
USB_DPRINTF_L3(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"usb_ac_send_as_cmd:"
" timeout happen before cmd complete.");
} else {
USB_DPRINTF_L3(PRINT_MASK_ALL,
uacp->usb_ac_log_handle,
"usb_ac_send_as_cmd:"
" not signaled by USB_AS_PLUMBED.");
}
}
#endif
}
/* Wait is over, get the reply data */
mp = streams_infop->acs_ac_to_as_req.acr_reply_mp;
ASSERT(mp != NULL);
iocp = (struct iocblk *)mp->b_rptr;
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_send_as_cmd: db_type=0x%x cmd=0x%x",
mp->b_datap->db_type, iocp->ioc_cmd);
switch (mp->b_datap->db_type) {
case M_CTL:
switch (iocp->ioc_cmd) {
case USB_AUDIO_SET_FORMAT:
/*
* This command sets mixer format data
* and returns alternate setting that matches
*/
ASSERT(mp->b_cont != NULL);
ASSERT((mp->b_cont->b_wptr - mp->b_cont->b_rptr) ==
sizeof (int));
USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"alternate returned %d",
*((int *)(mp->b_cont->b_rptr)));
streams_infop->acs_ac_to_as_req.acr_curr_format.
fmt_alt = *((int *)(mp->b_cont->b_rptr));
/*FALLTHROUGH*/
case USB_AUDIO_SET_SAMPLE_FREQ:
case USB_AUDIO_SETUP:
case USB_AUDIO_START_PLAY:
case USB_AUDIO_PAUSE_PLAY:
case USB_AUDIO_START_RECORD:
case USB_AUDIO_STOP_RECORD:
case USB_AUDIO_TEARDOWN:
error = USB_SUCCESS;
break;
default:
break;
}
break;
case M_ERROR:
default:
error = USB_FAILURE;
}
if (mp) {
usb_ac_free_mblk(mp);
streams_infop->acs_ac_to_as_req.acr_reply_mp = NULL;
}
return (error);
}
/*
* usb_ac_allocate_req_mblk:
* Allocate a message block with the specified M_CTL cmd,
* The 2nd mblk contains the data for the command with a length len
*/
static mblk_t *
usb_ac_allocate_req_mblk(usb_ac_state_t *uacp, int cmd, void *buf, uint_t len)
{
mblk_t *mp, *mp2;
struct iocblk *mctlmsg;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_allocate_req_mblk: cmd=0x%x, buf=0x%p, len=%d",
cmd, buf, len);
mp = allocb_wait(sizeof (struct iocblk), BPRI_HI, STR_NOSIG, NULL);
mp->b_datap->db_type = M_CTL;
mctlmsg = (struct iocblk *)mp->b_datap->db_base;
mctlmsg->ioc_cmd = cmd;
mctlmsg->ioc_count = len;
mp->b_wptr = mp->b_wptr + sizeof (struct iocblk);
if ((len == 0) || (buf == NULL)) {
return (mp);
}
mp2 = allocb_wait(len, BPRI_HI, STR_NOSIG, NULL);
mp->b_cont = mp2;
bcopy(buf, mp->b_cont->b_datap->db_base, len);
mp->b_cont->b_wptr = mp->b_cont->b_wptr + len;
USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle,
"usb_ac_allocate_req_mblk: mp=0x%p", (void *)mp);
return (mp);
}
/*
* usb_ac_free_mblk:
* Free the message block
*/
static void
usb_ac_free_mblk(mblk_t *mp)
{
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
freemsg(mp);
}
/*
* usb_ac_serialize/release_access:
*/
static void
usb_ac_serialize_access(usb_ac_state_t *uacp)
{
(void) usb_serialize_access(uacp->usb_ac_ser_acc, USB_WAIT, 0);
}
static void
usb_ac_release_access(usb_ac_state_t *uacp)
{
usb_release_access(uacp->usb_ac_ser_acc);
}
static void
usb_ac_pm_busy_component(usb_ac_state_t *usb_ac_statep)
{
ASSERT(!mutex_owned(&usb_ac_statep->usb_ac_mutex));
if (usb_ac_statep->usb_ac_pm != NULL) {
mutex_enter(&usb_ac_statep->usb_ac_mutex);
usb_ac_statep->usb_ac_pm->acpm_pm_busy++;
USB_DPRINTF_L4(PRINT_MASK_PM,
usb_ac_statep->usb_ac_log_handle,
"usb_ac_pm_busy_component: %d",
usb_ac_statep->usb_ac_pm->acpm_pm_busy);
mutex_exit(&usb_ac_statep->usb_ac_mutex);
if (pm_busy_component(usb_ac_statep->usb_ac_dip, 0) !=
DDI_SUCCESS) {
mutex_enter(&usb_ac_statep->usb_ac_mutex);
usb_ac_statep->usb_ac_pm->acpm_pm_busy--;
USB_DPRINTF_L2(PRINT_MASK_PM,
usb_ac_statep->usb_ac_log_handle,
"usb_ac_pm_busy_component failed: %d",
usb_ac_statep->usb_ac_pm->acpm_pm_busy);
mutex_exit(&usb_ac_statep->usb_ac_mutex);
}
}
}
static void
usb_ac_pm_idle_component(usb_ac_state_t *usb_ac_statep)
{
ASSERT(!mutex_owned(&usb_ac_statep->usb_ac_mutex));
if (usb_ac_statep->usb_ac_pm != NULL) {
if (pm_idle_component(usb_ac_statep->usb_ac_dip, 0) ==
DDI_SUCCESS) {
mutex_enter(&usb_ac_statep->usb_ac_mutex);
ASSERT(usb_ac_statep->usb_ac_pm->acpm_pm_busy > 0);
usb_ac_statep->usb_ac_pm->acpm_pm_busy--;
USB_DPRINTF_L4(PRINT_MASK_PM,
usb_ac_statep->usb_ac_log_handle,
"usb_ac_pm_idle_component: %d",
usb_ac_statep->usb_ac_pm->acpm_pm_busy);
mutex_exit(&usb_ac_statep->usb_ac_mutex);
}
}
}