/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2023 Oxide Computer Company */ /* * AUDIO CONTROL Driver: * * usb_ac is a multiplexor that sits on top of usb_as and hid and is * responsible for (1) providing the entry points to audio mixer framework, * (2) passing control commands to and from usb_as and hid and (3) processing * control messages from hid/usb_ah that it can handle. * * 1. Mixer entry points are: usb_ac_setup(), usb_ac_teardown(), * usb_ac_set_config(), usb_ac_set_format(), usb_ac_start_play(), * usb_ac_pause_play(), usb_ac_stop_play, usb_ac_start_record(), * usb_ac_stop_record(). * 2. usb_ac is a streams driver that passes streams messages down to * usb_as that selects the correct alternate with passed format * parameters, sets sample frequency, starts play/record, stops * play/record, pause play/record, open/close isoc pipe. * 3. usb_ac handles the set_config command through the default pipe * of sound control interface of the audio device in a synchronous * manner. * * Serialization: A competing thread can't be allowed to interfere with * (1) pipe, (2) streams state. * So we need some kind of serialization among the asynchronous * threads that can run in the driver. The serialization is mostly * needed to avoid races among open/close/events/power entry points * etc. Once a routine takes control, it checks if the resource (pipe or * stream or dev state) is still accessible. If so, it proceeds with * its job and until it completes, no other thread requiring the same * resource can run. * * PM model in usb_ac: Raise power during attach. If a device is not at full * power, raise power in the entry points. After the command is over, * pm_idle_component() is called. The power is lowered in detach(). */ #include #include #include #include #include #include #include #include #include #include #include #include /* for getting the minor node info from hid */ #include #include /* debug support */ uint_t usb_ac_errlevel = USB_LOG_L4; uint_t usb_ac_errmask = (uint_t)-1; uint_t usb_ac_instance_debug = (uint_t)-1; /* * wait period in seconds for the HID message processing thread * used primarily to check when the stream has closed */ uint_t usb_ac_wait_hid = 1; /* * table for converting term types of input and output terminals * to OSS port types (pretty rough mapping) */ static const char *usb_audio_dtypes[] = { AUDIO_PORT_LINEIN, AUDIO_PORT_LINEOUT, AUDIO_PORT_SPEAKER, AUDIO_PORT_HEADPHONES, AUDIO_PORT_HANDSET, AUDIO_PORT_CD, AUDIO_PORT_MIC, AUDIO_PORT_PHONE, AUDIO_PORT_SPDIFIN, AUDIO_PORT_OTHER, NULL, }; enum { USB_PORT_LINEIN = 0, USB_PORT_LINEOUT, USB_PORT_SPEAKER, USB_PORT_HEADPHONES, USB_PORT_HANDSET, USB_PORT_CD, USB_PORT_MIC, USB_PORT_PHONE, USB_PORT_SPDIFIN, USB_PORT_UNKNOWN }; static struct { ushort_t term_type; uint_t port_type; } usb_ac_term_type_map[] = { /* Input Terminal Types */ { USB_AUDIO_TERM_TYPE_MICROPHONE, USB_PORT_MIC }, { USB_AUDIO_TERM_TYPE_DT_MICROPHONE, USB_PORT_MIC }, { USB_AUDIO_TERM_TYPE_PERS_MICROPHONE, USB_PORT_MIC }, { USB_AUDIO_TERM_TYPE_OMNI_DIR_MICROPHONE, USB_PORT_MIC }, { USB_AUDIO_TERM_TYPE_MICROPHONE_ARRAY, USB_PORT_MIC }, { USB_AUDIO_TERM_TYPE_PROCESSING_MIC_ARRAY, USB_PORT_MIC }, /* Output Terminal Types */ { USB_AUDIO_TERM_TYPE_SPEAKER, USB_PORT_SPEAKER }, { USB_AUDIO_TERM_TYPE_HEADPHONES, USB_PORT_HEADPHONES }, { USB_AUDIO_TERM_TYPE_DISPLAY_AUDIO, USB_PORT_LINEOUT }, { USB_AUDIO_TERM_TYPE_DT_SPEAKER, USB_PORT_SPEAKER }, { USB_AUDIO_TERM_TYPE_ROOM_SPEAKER, USB_PORT_SPEAKER }, { USB_AUDIO_TERM_TYPE_COMM_SPEAKER, USB_PORT_SPEAKER }, { USB_AUDIO_TERM_TYPE_LF_EFFECTS_SPEAKER, USB_PORT_SPEAKER }, /* Bi-directional Terminal Types */ { USB_AUDIO_TERM_TYPE_HANDSET, USB_PORT_HANDSET }, /* Telephony Terminal Types */ { USB_AUDIO_TERM_TYPE_PHONE_LINE, USB_PORT_PHONE}, { USB_AUDIO_TERM_TYPE_TELEPHONE, USB_PORT_PHONE}, { USB_AUDIO_TERM_TYPE_DOWN_LINE_PHONE, USB_PORT_PHONE }, /* External Terminal Types */ { USB_AUDIO_TERM_TYPE_SPDIF_IF, USB_PORT_SPDIFIN }, /* Embedded Function Terminal Types */ { USB_AUDIO_TERM_TYPE_CD_PLAYER, USB_PORT_CD }, { 0, 0 } }; /* * Module linkage routines for the kernel */ static int usb_ac_attach(dev_info_t *, ddi_attach_cmd_t); static int usb_ac_detach(dev_info_t *, ddi_detach_cmd_t); static int usb_ac_power(dev_info_t *, int, int); static uint_t usb_ac_get_featureID(usb_ac_state_t *, uchar_t, uint_t, uint_t); /* module entry points */ int usb_ac_open(dev_info_t *); void usb_ac_close(dev_info_t *); /* descriptor handling */ static int usb_ac_handle_descriptors(usb_ac_state_t *); static void usb_ac_add_unit_descriptor(usb_ac_state_t *, uchar_t *, size_t); static void usb_ac_alloc_unit(usb_ac_state_t *, uint_t); static void usb_ac_free_all_units(usb_ac_state_t *); static void usb_ac_setup_connections(usb_ac_state_t *); static void usb_ac_map_termtype_to_port(usb_ac_state_t *, uint_t); /* power management */ static int usb_ac_pwrlvl0(usb_ac_state_t *); static int usb_ac_pwrlvl1(usb_ac_state_t *); static int usb_ac_pwrlvl2(usb_ac_state_t *); static int usb_ac_pwrlvl3(usb_ac_state_t *); static void usb_ac_create_pm_components(dev_info_t *, usb_ac_state_t *); static void usb_ac_pm_busy_component(usb_ac_state_t *); static void usb_ac_pm_idle_component(usb_ac_state_t *); /* event handling */ static int usb_ac_disconnect_event_cb(dev_info_t *); static int usb_ac_reconnect_event_cb(dev_info_t *); static int usb_ac_cpr_suspend(dev_info_t *); static void usb_ac_cpr_resume(dev_info_t *); static usb_event_t usb_ac_events = { usb_ac_disconnect_event_cb, usb_ac_reconnect_event_cb, NULL, NULL }; /* misc. support */ static void usb_ac_restore_device_state(dev_info_t *, usb_ac_state_t *); static int usb_ac_cleanup(dev_info_t *, usb_ac_state_t *); static void usb_ac_serialize_access(usb_ac_state_t *); static void usb_ac_release_access(usb_ac_state_t *); static void usb_ac_push_unit_id(usb_ac_state_t *, uint_t); static void usb_ac_pop_unit_id(usb_ac_state_t *, uint_t); static void usb_ac_show_traverse_path(usb_ac_state_t *); static int usb_ac_check_path(usb_ac_state_t *, uint_t); static uint_t usb_ac_traverse_connections(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *, uint_t, uint_t *, int (*func)(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *)); static uint_t usb_ac_set_port(usb_ac_state_t *, uint_t, uint_t); static uint_t usb_ac_set_control(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *, uint_t, int (*func)(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *)); static uint_t usb_ac_set_monitor_gain_control(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *, uint_t, int (*func)(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *)); static uint_t usb_ac_traverse_all_units(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *, uint_t, uint_t *, int (*func)(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *)); static int usb_ac_update_port(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *); static int usb_ac_set_selector(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *); static int usb_ac_feature_unit_check(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *); static int usb_ac_set_gain(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *); static int usb_ac_set_monitor_gain(usb_ac_state_t *, uint_t, uint_t, uint_t, uint_t, uint_t, uint_t *); static int usb_ac_set_volume(usb_ac_state_t *, uint_t, short, int dir, int); static int usb_ac_get_maxmin_volume(usb_ac_state_t *, uint_t, int, int, int, short *); static int usb_ac_send_as_cmd(usb_ac_state_t *, usb_audio_eng_t *, int, void *); static int usb_ac_set_format(usb_ac_state_t *, usb_audio_eng_t *); static int usb_ac_do_setup(usb_ac_state_t *, usb_audio_eng_t *); /* usb audio basic function entries */ static int usb_ac_setup(usb_ac_state_t *, usb_audio_eng_t *); static void usb_ac_teardown(usb_ac_state_t *, usb_audio_eng_t *); static int usb_ac_start_play(usb_ac_state_t *, usb_audio_eng_t *); static int usb_ac_start_record(usb_ac_state_t *, usb_audio_eng_t *); static void usb_ac_stop_record(usb_ac_state_t *, usb_audio_eng_t *); static int usb_ac_restore_audio_state(usb_ac_state_t *, int); static int usb_ac_ctrl_restore(usb_ac_state_t *); /* * Mux */ static int usb_ac_mux_walk_siblings(usb_ac_state_t *); static void usb_ac_print_reg_data(usb_ac_state_t *, usb_as_registration_t *); static int usb_ac_get_reg_data(usb_ac_state_t *, ldi_handle_t, int); static int usb_ac_setup_plumbed(usb_ac_state_t *, int, int); static int usb_ac_mixer_registration(usb_ac_state_t *); static void usb_ac_hold_siblings(usb_ac_state_t *); static int usb_ac_online_siblings(usb_ac_state_t *); static void usb_ac_rele_siblings(usb_ac_state_t *); static int usb_ac_mux_plumbing(usb_ac_state_t *); static void usb_ac_mux_plumbing_tq(void *); static int usb_ac_mux_unplumbing(usb_ac_state_t *); static void usb_ac_mux_unplumbing_tq(void *); static int usb_ac_plumb(usb_ac_plumbed_t *); static void usb_ac_unplumb(usb_ac_plumbed_t *); static void usb_ac_reader(void *); static int usb_ac_read_msg(usb_ac_plumbed_t *, mblk_t *); static int usb_ac_do_plumbing(usb_ac_state_t *); static int usb_ac_do_unplumbing(usb_ac_state_t *); static int usb_change_phy_vol(usb_ac_state_t *, int); static void usb_restore_engine(usb_ac_state_t *); /* anchor for soft state structures */ void *usb_ac_statep; /* * DDI Structures */ /* Device operations structure */ static struct dev_ops usb_ac_dev_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ NULL, /* devo_getinfo */ nulldev, /* devo_identify - obsolete */ nulldev, /* devo_probe - not needed */ usb_ac_attach, /* devo_attach */ usb_ac_detach, /* devo_detach */ nodev, /* devo_reset */ NULL, /* devi_cb_ops */ NULL, /* devo_busb_ac_ops */ usb_ac_power, /* devo_power */ ddi_quiesce_not_needed, /* devo_quiesce */ }; /* Linkage structure for loadable drivers */ static struct modldrv usb_ac_modldrv = { &mod_driverops, /* drv_modops */ "USB Audio Control Driver", /* drv_linkinfo */ &usb_ac_dev_ops /* drv_dev_ops */ }; /* Module linkage structure */ static struct modlinkage usb_ac_modlinkage = { MODREV_1, /* ml_rev */ (void *)&usb_ac_modldrv, /* ml_linkage */ NULL /* NULL terminates the list */ }; static int usb_audio_register(usb_ac_state_t *); static int usb_audio_unregister(usb_ac_state_t *); static int usb_engine_open(void *, int, unsigned *, caddr_t *); static void usb_engine_close(void *); static uint64_t usb_engine_count(void *); static int usb_engine_start(void *); static void usb_engine_stop(void *); static int usb_engine_format(void *); static int usb_engine_channels(void *); static int usb_engine_rate(void *); static void usb_engine_sync(void *, unsigned); static unsigned usb_engine_qlen(void *); /* engine buffer size in terms of fragments */ audio_engine_ops_t usb_engine_ops = { AUDIO_ENGINE_VERSION, usb_engine_open, usb_engine_close, usb_engine_start, usb_engine_stop, usb_engine_count, usb_engine_format, usb_engine_channels, usb_engine_rate, usb_engine_sync, usb_engine_qlen, }; _NOTE(SCHEME_PROTECTS_DATA("unique per call", mblk_t)) /* standard entry points */ int _init(void) { int rval; /* initialize the soft state */ if ((rval = ddi_soft_state_init(&usb_ac_statep, sizeof (usb_ac_state_t), 1)) != DDI_SUCCESS) { return (rval); } audio_init_ops(&usb_ac_dev_ops, "usb_ac"); if ((rval = mod_install(&usb_ac_modlinkage)) != 0) { ddi_soft_state_fini(&usb_ac_statep); audio_fini_ops(&usb_ac_dev_ops); } return (rval); } int _fini(void) { int rval; if ((rval = mod_remove(&usb_ac_modlinkage)) == 0) { /* Free the soft state internal structures */ ddi_soft_state_fini(&usb_ac_statep); audio_fini_ops(&usb_ac_dev_ops); } return (rval); } int _info(struct modinfo *modinfop) { return (mod_info(&usb_ac_modlinkage, modinfop)); } extern uint_t nproc; #define INIT_PROCESS_CNT 3 static int usb_ac_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { usb_ac_state_t *uacp = NULL; int instance = ddi_get_instance(dip); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: usb_ac_cpr_resume(dip); return (DDI_SUCCESS); default: return (DDI_FAILURE); } /* * wait until all processes are started from main. * USB enumerates early in boot (ie. consconfig time). * If the plumbing takes place early, the file descriptors * are owned by the init process and can never be closed anymore * Consequently, hot removal is not possible and the dips * never go away. By waiting some time, e.g. INIT_PROCESS_CNT, * the problem is avoided. */ if (nproc < INIT_PROCESS_CNT) { USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL, "usb_ac%d attach too early", instance); return (DDI_FAILURE); } /* * Allocate soft state information. */ if (ddi_soft_state_zalloc(usb_ac_statep, instance) != DDI_SUCCESS) { goto fail; } /* * get soft state space and initialize */ uacp = (usb_ac_state_t *)ddi_get_soft_state(usb_ac_statep, instance); if (uacp == NULL) { goto fail; } /* get log handle */ uacp->usb_ac_log_handle = usb_alloc_log_hdl(dip, "ac", &usb_ac_errlevel, &usb_ac_errmask, &usb_ac_instance_debug, 0); uacp->usb_ac_instance = instance; uacp->usb_ac_dip = dip; (void) snprintf(uacp->dstr, sizeof (uacp->dstr), "%s#%d", ddi_driver_name(dip), instance); if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_client_attach failed"); usb_free_log_hdl(uacp->usb_ac_log_handle); ddi_soft_state_free(usb_ac_statep, uacp->usb_ac_instance); return (DDI_FAILURE); } if (usb_get_dev_data(dip, &uacp->usb_ac_dev_data, USB_PARSE_LVL_IF, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_get_dev_data failed"); usb_client_detach(dip, NULL); usb_free_log_hdl(uacp->usb_ac_log_handle); ddi_soft_state_free(usb_ac_statep, uacp->usb_ac_instance); return (DDI_FAILURE); } /* initialize mutex & cv */ mutex_init(&uacp->usb_ac_mutex, NULL, MUTEX_DRIVER, uacp->usb_ac_dev_data->dev_iblock_cookie); uacp->usb_ac_default_ph = uacp->usb_ac_dev_data->dev_default_ph; /* parse all class specific descriptors */ if (usb_ac_handle_descriptors(uacp) != USB_SUCCESS) { goto fail; } /* we no longer need the descr tree */ usb_free_descr_tree(dip, uacp->usb_ac_dev_data); uacp->usb_ac_ser_acc = usb_init_serialization(dip, USB_INIT_SER_CHECK_SAME_THREAD); mutex_enter(&uacp->usb_ac_mutex); /* we are online */ uacp->usb_ac_dev_state = USB_DEV_ONLINE; /* * safe guard the postattach to be executed * only two states arepossible: plumbed / unplumbed */ uacp->usb_ac_plumbing_state = USB_AC_STATE_UNPLUMBED; uacp->usb_ac_current_plumbed_index = -1; mutex_exit(&uacp->usb_ac_mutex); /* create components to power manage this device */ usb_ac_create_pm_components(dip, uacp); /* Register for events */ if (usb_register_event_cbs(dip, &usb_ac_events, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_attach: couldn't register for events"); goto fail; } USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_attach: End"); /* report device */ ddi_report_dev(dip); if (usb_ac_do_plumbing(uacp) != USB_SUCCESS) goto fail; return (DDI_SUCCESS); fail: if (uacp) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "attach failed"); /* wait for plumbing thread to finish */ if (uacp->tqp != NULL) { ddi_taskq_wait(uacp->tqp); ddi_taskq_destroy(uacp->tqp); uacp->tqp = NULL; } (void) usb_ac_cleanup(dip, uacp); } return (DDI_FAILURE); } static int usb_ac_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(dip); usb_ac_state_t *uacp; int rval = USB_FAILURE; uacp = ddi_get_soft_state(usb_ac_statep, instance); switch (cmd) { case DDI_DETACH: USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_detach: detach"); /* wait for plumbing thread to finish */ if (uacp->tqp != NULL) ddi_taskq_wait(uacp->tqp); mutex_enter(&uacp->usb_ac_mutex); /* do not allow detach if still busy */ if (uacp->usb_ac_busy_count) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_detach:still busy, usb_ac_busy_count = %d", uacp->usb_ac_busy_count); mutex_exit(&uacp->usb_ac_mutex); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); (void) usb_audio_unregister(uacp); /* * unplumb to stop activity from other modules, then * cleanup, which will also teardown audio framework state */ if (usb_ac_do_unplumbing(uacp) == USB_SUCCESS) rval = usb_ac_cleanup(dip, uacp); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "detach failed: %s%d", ddi_driver_name(dip), instance); } return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); case DDI_SUSPEND: USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_detach: suspending"); rval = usb_ac_cpr_suspend(dip); return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); default: return (DDI_FAILURE); } } /* * usb_ac_cleanup: * cleanup on attach failure and detach */ static int usb_ac_cleanup(dev_info_t *dip, usb_ac_state_t *uacp) { usb_ac_power_t *uacpm; int rval = USB_FAILURE; mutex_enter(&uacp->usb_ac_mutex); uacpm = uacp->usb_ac_pm; USB_DPRINTF_L4(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_cleanup:begain"); ASSERT(uacp->usb_ac_busy_count == 0); ASSERT(uacp->usb_ac_plumbing_state == USB_AC_STATE_UNPLUMBED); mutex_exit(&uacp->usb_ac_mutex); /* * Disable the event callbacks, after this point, event * callbacks will never get called. Note we shouldn't hold * the mutex while unregistering events because there may be a * competing event callback thread. Event callbacks are done * with ndi mutex held and this can cause a potential deadlock. */ usb_unregister_event_cbs(dip, &usb_ac_events); mutex_enter(&uacp->usb_ac_mutex); if (uacpm && (uacp->usb_ac_dev_state != USB_DEV_DISCONNECTED)) { if (uacpm->acpm_wakeup_enabled) { mutex_exit(&uacp->usb_ac_mutex); usb_ac_pm_busy_component(uacp); (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); rval = usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_DISABLE); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_PM, uacp->usb_ac_log_handle, "usb_ac_cleanup: disable remote " "wakeup failed, rval=%d", rval); } usb_ac_pm_idle_component(uacp); } else { mutex_exit(&uacp->usb_ac_mutex); } (void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF); mutex_enter(&uacp->usb_ac_mutex); } if (uacpm) { kmem_free(uacpm, sizeof (usb_ac_power_t)); uacp->usb_ac_pm = NULL; } usb_client_detach(dip, uacp->usb_ac_dev_data); /* free descriptors */ usb_ac_free_all_units(uacp); mutex_exit(&uacp->usb_ac_mutex); mutex_destroy(&uacp->usb_ac_mutex); usb_fini_serialization(uacp->usb_ac_ser_acc); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_cleanup: Ending"); usb_free_log_hdl(uacp->usb_ac_log_handle); kmem_free(uacp->usb_ac_connections, uacp->usb_ac_connections_len); kmem_free(uacp->usb_ac_connections_a, uacp->usb_ac_connections_a_len); kmem_free(uacp->usb_ac_unit_type, uacp->usb_ac_max_unit); kmem_free(uacp->usb_ac_traverse_path, uacp->usb_ac_max_unit); ddi_soft_state_free(usb_ac_statep, uacp->usb_ac_instance); ddi_prop_remove_all(dip); return (USB_SUCCESS); } int usb_ac_open(dev_info_t *dip) { int inst = ddi_get_instance(dip); usb_ac_state_t *uacp = ddi_get_soft_state(usb_ac_statep, inst); mutex_enter(&uacp->usb_ac_mutex); uacp->usb_ac_busy_count++; mutex_exit(&uacp->usb_ac_mutex); usb_ac_pm_busy_component(uacp); (void) pm_raise_power(uacp->usb_ac_dip, 0, USB_DEV_OS_FULL_PWR); return (0); } void usb_ac_close(dev_info_t *dip) { int inst = ddi_get_instance(dip); usb_ac_state_t *uacp = ddi_get_soft_state(usb_ac_statep, inst); mutex_enter(&uacp->usb_ac_mutex); if (uacp->usb_ac_busy_count > 0) uacp->usb_ac_busy_count--; mutex_exit(&uacp->usb_ac_mutex); usb_ac_pm_idle_component(uacp); } /* * usb_ac_read_msg: * Handle asynchronous response from opened streams */ static int usb_ac_read_msg(usb_ac_plumbed_t *plumb_infop, mblk_t *mp) { usb_ac_state_t *uacp = plumb_infop->acp_uacp; int error = DDI_SUCCESS; int val; char val1; struct iocblk *iocp; ASSERT(mutex_owned(&uacp->usb_ac_mutex)); /* * typically an M_CTL is used between modules but in order to pass * through the streamhead, an M_PROTO type must be used instead */ switch (mp->b_datap->db_type) { case M_PROTO: case M_ERROR: USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "M_CTL/M_ERROR"); switch (plumb_infop->acp_driver) { case USB_AH_PLUMBED: USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "message from hid, instance=%d", ddi_get_instance(plumb_infop->acp_dip)); iocp = (struct iocblk *)(void *)mp->b_rptr; ASSERT(mp->b_cont != NULL); if (uacp->usb_ac_registered_with_mixer) { val1 = *((char *)mp->b_cont->b_rptr); val = (int)val1; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "val1=0x%x(%d)," "val=0x%x(%d)", val1, val1, val, val); switch (iocp->ioc_cmd) { /* Handle relative volume change */ case USB_AUDIO_VOL_CHANGE: /* prevent unplumbing */ uacp->usb_ac_busy_count++; if (uacp->usb_ac_plumbing_state == USB_AC_STATE_PLUMBED) { mutex_exit(&uacp->usb_ac_mutex); (void) usb_change_phy_vol( uacp, val); mutex_enter(&uacp-> usb_ac_mutex); } uacp->usb_ac_busy_count--; /* FALLTHRU */ case USB_AUDIO_MUTE: default: freemsg(mp); break; } } else { freemsg(mp); } break; default: USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "message from unknown module(%s)", ddi_driver_name(plumb_infop->acp_dip)); freemsg(mp); } break; default: USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "Unknown type=%d", mp->b_datap->db_type); freemsg(mp); } return (error); } /* * Power Management * usb_ac_power: * power entry point */ static int usb_ac_power(dev_info_t *dip, int comp, int level) { _NOTE(ARGUNUSED(comp)); int instance = ddi_get_instance(dip); usb_ac_state_t *uacp; usb_ac_power_t *uacpm; int rval = DDI_FAILURE; uacp = ddi_get_soft_state(usb_ac_statep, instance); mutex_enter(&uacp->usb_ac_mutex); uacpm = uacp->usb_ac_pm; if (USB_DEV_PWRSTATE_OK(uacpm->acpm_pwr_states, level)) { USB_DPRINTF_L2(PRINT_MASK_PM, uacp->usb_ac_log_handle, "usb_ac_power: illegal level=%d pwr_states=%d", level, uacpm->acpm_pwr_states); goto done; } switch (level) { case USB_DEV_OS_PWR_OFF: rval = usb_ac_pwrlvl0(uacp); break; case USB_DEV_OS_PWR_1: rval = usb_ac_pwrlvl1(uacp); break; case USB_DEV_OS_PWR_2: rval = usb_ac_pwrlvl2(uacp); break; case USB_DEV_OS_FULL_PWR: rval = usb_ac_pwrlvl3(uacp); break; } done: mutex_exit(&uacp->usb_ac_mutex); return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); } /* * functions to handle power transition for various levels * These functions act as place holders to issue USB commands * to the devices to change their power levels * Level 0 = Device is powered off * Level 3 = Device if full powered * Level 1,2 = Intermediate power level of the device as implemented * by the hardware. * Note that Level 0 is OS power-off and Level 3 is OS full-power. */ static int usb_ac_pwrlvl0(usb_ac_state_t *uacp) { usb_ac_power_t *uacpm; int rval; uacpm = uacp->usb_ac_pm; switch (uacp->usb_ac_dev_state) { case USB_DEV_ONLINE: /* Deny the powerdown request if the device is busy */ if (uacpm->acpm_pm_busy != 0) { return (USB_FAILURE); } /* Issue USB D3 command to the device here */ rval = usb_set_device_pwrlvl3(uacp->usb_ac_dip); ASSERT(rval == USB_SUCCESS); uacp->usb_ac_dev_state = USB_DEV_PWRED_DOWN; uacpm->acpm_current_power = USB_DEV_OS_PWR_OFF; /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: case USB_DEV_PWRED_DOWN: default: return (USB_SUCCESS); } } /* ARGSUSED */ static int usb_ac_pwrlvl1(usb_ac_state_t *uacp) { int rval; /* Issue USB D2 command to the device here */ rval = usb_set_device_pwrlvl2(uacp->usb_ac_dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } /* ARGSUSED */ static int usb_ac_pwrlvl2(usb_ac_state_t *uacp) { int rval; rval = usb_set_device_pwrlvl1(uacp->usb_ac_dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } static int usb_ac_pwrlvl3(usb_ac_state_t *uacp) { usb_ac_power_t *uacpm; int rval; uacpm = uacp->usb_ac_pm; switch (uacp->usb_ac_dev_state) { case USB_DEV_PWRED_DOWN: /* Issue USB D0 command to the device here */ rval = usb_set_device_pwrlvl0(uacp->usb_ac_dip); ASSERT(rval == USB_SUCCESS); uacp->usb_ac_dev_state = USB_DEV_ONLINE; uacpm->acpm_current_power = USB_DEV_OS_FULL_PWR; /* FALLTHRU */ case USB_DEV_ONLINE: /* we are already in full power */ /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: return (USB_SUCCESS); default: USB_DPRINTF_L2(PRINT_MASK_PM, uacp->usb_ac_log_handle, "usb_ac_pwerlvl3: Illegal dev_state"); return (USB_FAILURE); } } static void usb_ac_create_pm_components(dev_info_t *dip, usb_ac_state_t *uacp) { usb_ac_power_t *uacpm; uint_t pwr_states; USB_DPRINTF_L4(PRINT_MASK_PM, uacp->usb_ac_log_handle, "usb_ac_create_pm_components: begin"); /* Allocate the state structure */ uacpm = kmem_zalloc(sizeof (usb_ac_power_t), KM_SLEEP); uacp->usb_ac_pm = uacpm; uacpm->acpm_state = uacp; uacpm->acpm_capabilities = 0; uacpm->acpm_current_power = USB_DEV_OS_FULL_PWR; if (usb_create_pm_components(dip, &pwr_states) == USB_SUCCESS) { if (usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) { uacpm->acpm_wakeup_enabled = 1; USB_DPRINTF_L4(PRINT_MASK_PM, uacp->usb_ac_log_handle, "remote Wakeup enabled"); } uacpm->acpm_pwr_states = (uint8_t)pwr_states; (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); } else { if (uacpm) { kmem_free(uacpm, sizeof (usb_ac_power_t)); uacp->usb_ac_pm = NULL; } USB_DPRINTF_L2(PRINT_MASK_PM, uacp->usb_ac_log_handle, "pm not enabled"); } } /* * usb_ac_get_featureID: * find out if there is at least one feature unit that supports * the request controls. * Return featureID or USB_AC_ID_NONE. */ static uint_t usb_ac_get_featureID(usb_ac_state_t *uacp, uchar_t dir, uint_t channel, uint_t control) { uint_t count = 0; return (usb_ac_set_control(uacp, dir, USB_AUDIO_FEATURE_UNIT, channel, control, USB_AC_FIND_ONE, &count, 0, usb_ac_feature_unit_check)); } /* * usb_ac_feature_unit_check: * check if a feature unit can support the required channel * and control combination. Return USB_SUCCESS or USB_FAILURE. * Called for each matching unit from usb_ac_traverse_connections. */ /*ARGSUSED*/ static int usb_ac_feature_unit_check(usb_ac_state_t *uacp, uint_t featureID, uint_t dir, uint_t channel, uint_t control, uint_t arg1, uint_t *depth) { usb_audio_feature_unit_descr1_t *feature_descrp; int n_channel_controls; ASSERT(featureID < uacp->usb_ac_max_unit); /* * check if this control is supported on this channel */ feature_descrp = (usb_audio_feature_unit_descr1_t *) uacp->usb_ac_units[featureID].acu_descriptor; ASSERT(feature_descrp->bUnitID == featureID); USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "bControlSize=%d", feature_descrp->bControlSize); if (feature_descrp->bControlSize == 0) { featureID = USB_AC_ID_NONE; } else { uint_t index; n_channel_controls = (feature_descrp->bLength - offsetof(usb_audio_feature_unit_descr1_t, bmaControls))/feature_descrp->bControlSize; USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "#controls: %d index=%d", n_channel_controls, feature_descrp->bControlSize * channel); if (channel > n_channel_controls) { featureID = USB_AC_ID_NONE; } else { /* * we only support MUTE and VOLUME * which are in the first byte */ index = feature_descrp->bControlSize * channel; USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "control: 0x%x", feature_descrp->bmaControls[index]); if ((feature_descrp->bmaControls[index] & control) == 0) { featureID = USB_AC_ID_NONE; } } } USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_feature_unit_check: dir=%d featureID=0x%x", dir, featureID); return ((featureID != USB_AC_ID_NONE) ? USB_SUCCESS : USB_FAILURE); } /* * Descriptor Management * * usb_ac_handle_descriptors: * extract interesting descriptors from the config cloud */ static int usb_ac_handle_descriptors(usb_ac_state_t *uacp) { int len, index; int rval = USB_FAILURE; usb_audio_cs_if_descr_t descr; usb_client_dev_data_t *dev_data = uacp->usb_ac_dev_data; usb_alt_if_data_t *altif_data; usb_cvs_data_t *cvs; altif_data = &dev_data->dev_curr_cfg-> cfg_if[dev_data->dev_curr_if].if_alt[0]; uacp->usb_ac_ifno = dev_data->dev_curr_if; uacp->usb_ac_if_descr = altif_data->altif_descr; /* find USB_AUDIO_CS_INTERFACE type descriptor */ for (index = 0; index < altif_data->altif_n_cvs; index++) { cvs = &altif_data->altif_cvs[index]; if (cvs->cvs_buf == NULL) { continue; } if (cvs->cvs_buf[1] == USB_AUDIO_CS_INTERFACE) { break; } } if (index == altif_data->altif_n_cvs) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_handle_descriptors:cannot find descriptor type %d", USB_AUDIO_CS_INTERFACE); return (rval); } len = usb_parse_data( CS_AC_IF_HEADER_FORMAT, cvs->cvs_buf, cvs->cvs_buf_len, (void *)&descr, sizeof (usb_audio_cs_if_descr_t)); /* is this a sane header descriptor */ if (!((len >= CS_AC_IF_HEADER_SIZE) && (descr.bDescriptorType == USB_AUDIO_CS_INTERFACE) && (descr.bDescriptorSubType == USB_AUDIO_HEADER))) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "invalid header"); return (rval); } USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "index %d, header: type=0x%x subtype=0x%x bcdADC=0x%x\n\t" "total=0x%x InCol=0x%x", index, descr.bDescriptorType, descr.bDescriptorSubType, descr.bcdADC, descr.wTotalLength, descr.blnCollection); /* * we read descriptors by index and store them in ID array. * the actual parsing is done in usb_ac_add_unit_descriptor() */ for (index++; index < altif_data->altif_n_cvs; index++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "index=%d", index); cvs = &altif_data->altif_cvs[index]; if (cvs->cvs_buf == NULL) { continue; } /* add to ID array */ usb_ac_add_unit_descriptor(uacp, cvs->cvs_buf, cvs->cvs_buf_len); } rval = USB_SUCCESS; usb_ac_setup_connections(uacp); /* determine port types */ usb_ac_map_termtype_to_port(uacp, USB_AUDIO_PLAY); usb_ac_map_termtype_to_port(uacp, USB_AUDIO_RECORD); return (rval); } /* * usb_ac_setup_connections: * build a matrix reflecting all connections */ static void usb_ac_setup_connections(usb_ac_state_t *uacp) { usb_ac_unit_list_t *units = uacp->usb_ac_units; uchar_t *a, **p, i, unit; size_t a_len, p_len; /* allocate array for unit types for quick reference */ uacp->usb_ac_unit_type = kmem_zalloc(uacp->usb_ac_max_unit, KM_SLEEP); /* allocate array for traversal path */ uacp->usb_ac_traverse_path = kmem_zalloc(uacp->usb_ac_max_unit, KM_SLEEP); /* allocate the connection matrix and set it up */ a_len = uacp->usb_ac_max_unit * uacp->usb_ac_max_unit; p_len = uacp->usb_ac_max_unit * sizeof (uchar_t *); /* trick to create a 2 dimensional array */ a = kmem_zalloc(a_len, KM_SLEEP); p = kmem_zalloc(p_len, KM_SLEEP); for (i = 0; i < uacp->usb_ac_max_unit; i++) { p[i] = a + i * uacp->usb_ac_max_unit; } uacp->usb_ac_connections = p; uacp->usb_ac_connections_len = p_len; uacp->usb_ac_connections_a = a; uacp->usb_ac_connections_a_len = a_len; /* traverse all units and set connections */ for (unit = 0; unit < uacp->usb_ac_max_unit; unit++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "--------traversing unit=0x%x type=0x%x--------", unit, units[unit].acu_type); /* store type in the first unused column */ uacp->usb_ac_unit_type[unit] = units[unit].acu_type; /* save the Unit ID in the unit it points to */ switch (units[unit].acu_type) { case USB_AUDIO_FEATURE_UNIT: { usb_audio_feature_unit_descr1_t *d = units[unit].acu_descriptor; USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "USB_AUDIO_FEATURE_UNIT:sourceID=0x%x type=0x%x", d->bSourceID, units[d->bSourceID].acu_type); if (d->bSourceID != 0) { ASSERT(p[unit][d->bSourceID] == B_FALSE); p[unit][d->bSourceID] = B_TRUE; } break; } case USB_AUDIO_OUTPUT_TERMINAL: { usb_audio_output_term_descr_t *d = units[unit].acu_descriptor; USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "USB_AUDIO_OUTPUT_TERMINAL:sourceID=0x%x type=0x%x", d->bSourceID, units[d->bSourceID].acu_type); if (d->bSourceID != 0) { ASSERT(p[unit][d->bSourceID] == B_FALSE); p[unit][d->bSourceID] = B_TRUE; } break; } case USB_AUDIO_MIXER_UNIT: { usb_audio_mixer_unit_descr1_t *d = units[unit].acu_descriptor; int n_sourceID = d->bNrInPins; int id; for (id = 0; id < n_sourceID; id++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "USB_AUDIO_MIXER_UNIT:sourceID=0x%x" "type=0x%x c=%d", d->baSourceID[id], units[d->baSourceID[id]].acu_type, p[unit][d->baSourceID[id]]); if (d->baSourceID[id] != 0) { ASSERT(p[unit][d->baSourceID[id]] == B_FALSE); p[unit][d->baSourceID[id]] = B_TRUE; } } break; } case USB_AUDIO_SELECTOR_UNIT: { usb_audio_selector_unit_descr1_t *d = units[unit].acu_descriptor; int n_sourceID = d->bNrInPins; int id; for (id = 0; id < n_sourceID; id++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "USB_AUDIO_SELECTOR_UNIT:sourceID=0x%x" " type=0x%x", d->baSourceID[id], units[d->baSourceID[id]].acu_type); if (d->baSourceID[id] != 0) { ASSERT(p[unit][d->baSourceID[id]] == B_FALSE); p[unit][d->baSourceID[id]] = B_TRUE; } } break; } case USB_AUDIO_PROCESSING_UNIT: { usb_audio_mixer_unit_descr1_t *d = units[unit].acu_descriptor; int n_sourceID = d->bNrInPins; int id; for (id = 0; id < n_sourceID; id++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "USB_AUDIO_PROCESSING_UNIT:sourceID=0x%x" " type=0x%x", d->baSourceID[id], units[d->baSourceID[id]].acu_type); if (d->baSourceID[id] != 0) { ASSERT(p[unit][d->baSourceID[id]] == B_FALSE); p[unit][d->baSourceID[id]] = B_TRUE; } } break; } case USB_AUDIO_EXTENSION_UNIT: { usb_audio_extension_unit_descr1_t *d = units[unit].acu_descriptor; int n_sourceID = d->bNrInPins; int id; for (id = 0; id < n_sourceID; id++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "USB_AUDIO_EXTENSION_UNIT:sourceID=0x%x" "type=0x%x", d->baSourceID[id], units[d->baSourceID[id]].acu_type); if (d->baSourceID[id] != 0) { ASSERT(p[unit][d->baSourceID[id]] == B_TRUE); p[unit][d->baSourceID[id]] = B_FALSE; } } break; } case USB_AUDIO_INPUT_TERMINAL: break; default: /* * Ignore the rest because they are not support yet */ break; } } #ifdef DEBUG /* display topology in log buffer */ { uint_t i, j, l; char *buf; l = uacp->usb_ac_max_unit * 5; buf = kmem_alloc(l, KM_SLEEP); USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "unit types:"); /* two strings so they won't be replaced accidentily by tab */ (void) sprintf(&buf[0], " "" "); for (i = 1; i < uacp->usb_ac_max_unit; i++) { (void) sprintf(&buf[2 + (i*3)], "%02d ", i); } USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf); (void) sprintf(&buf[0], " +-------"); for (i = 1; i < uacp->usb_ac_max_unit; i++) { (void) sprintf(&buf[5+((i-1)*3)], "---"); } USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf); (void) sprintf(&buf[0], " "" "); for (i = 1; i < uacp->usb_ac_max_unit; i++) { (void) sprintf(&buf[2 + (i*3)], "%02d ", uacp->usb_ac_unit_type[i]); } USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf); USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, " "); USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "adjacency matrix:"); (void) sprintf(&buf[0], " "" "); for (i = 1; i < uacp->usb_ac_max_unit; i++) { (void) sprintf(&buf[2 + (i*3)], "%02d ", i); } USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf); (void) sprintf(&buf[0], " +-------"); for (i = 1; i < uacp->usb_ac_max_unit; i++) { (void) sprintf(&buf[5+((i-1)*3)], "---"); } USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf); for (i = 1; i < uacp->usb_ac_max_unit; i++) { (void) sprintf(&buf[0], "%02d| "" ", i); for (j = 1; j < uacp->usb_ac_max_unit; j++) { (void) sprintf(&buf[1+(j * 3)], "%2d ", p[i][j]); } USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, buf); } kmem_free(buf, l); } #endif } /* * usb_ac_add_unit_descriptor: * take the parsed descriptor in the buffer and store it in the ID unit * array. we grow the unit array if the ID exceeds the current max */ static void usb_ac_add_unit_descriptor(usb_ac_state_t *uacp, uchar_t *buffer, size_t buflen) { void *descr; int len; char *format; size_t size; /* doubling the length should allow for padding */ len = 2 * buffer[0]; descr = kmem_zalloc(len, KM_SLEEP); switch (buffer[2]) { case USB_AUDIO_INPUT_TERMINAL: format = CS_AC_INPUT_TERM_FORMAT; size = CS_AC_INPUT_TERM_SIZE; break; case USB_AUDIO_OUTPUT_TERMINAL: format = CS_AC_OUTPUT_TERM_FORMAT; size = CS_AC_OUTPUT_TERM_SIZE; break; case USB_AUDIO_MIXER_UNIT: format = CS_AC_MIXER_UNIT_DESCR1_FORMAT "255c"; size = CS_AC_MIXER_UNIT_DESCR1_SIZE + buffer[4] - 1; break; case USB_AUDIO_SELECTOR_UNIT: format = CS_AC_SELECTOR_UNIT_DESCR1_FORMAT "255c"; size = CS_AC_SELECTOR_UNIT_DESCR1_SIZE + buffer[4] - 1; break; case USB_AUDIO_FEATURE_UNIT: format = CS_AC_FEATURE_UNIT_FORMAT "255c"; size = CS_AC_FEATURE_UNIT_SIZE; break; case USB_AUDIO_PROCESSING_UNIT: format = CS_AC_PROCESSING_UNIT_DESCR1_FORMAT "255c"; size = CS_AC_PROCESSING_UNIT_DESCR1_SIZE + buffer[6] - 1; break; case USB_AUDIO_EXTENSION_UNIT: format = CS_AC_EXTENSION_UNIT_DESCR1_FORMAT "255c"; size = CS_AC_EXTENSION_UNIT_DESCR1_SIZE + buffer[6] - 1; break; default: USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "unsupported descriptor %d", buffer[2]); /* ignore this descriptor */ kmem_free(descr, len); return; } if (usb_parse_data(format, buffer, buflen, descr, len) < size) { /* ignore this descriptor */ kmem_free(descr, len); return; } switch (buffer[2]) { case USB_AUDIO_INPUT_TERMINAL: { usb_audio_input_term_descr_t *d = (usb_audio_input_term_descr_t *)descr; USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_units[%d] ---input term: type=0x%x sub=0x%x" "termid=0x%x\n\t" "termtype=0x%x assoc=0x%x #ch=%d " "chconf=0x%x ich=0x%x iterm=0x%x", d->bTerminalID, d->bDescriptorType, d->bDescriptorSubType, d->bTerminalID, d->wTerminalType, d->bAssocTerminal, d->bNrChannels, d->wChannelConfig, d->iChannelNames, d->iTerminal); usb_ac_alloc_unit(uacp, d->bTerminalID); uacp->usb_ac_units[d->bTerminalID].acu_descriptor = descr; uacp->usb_ac_units[d->bTerminalID].acu_type = buffer[2]; uacp->usb_ac_units[d->bTerminalID].acu_descr_length = len; break; } case USB_AUDIO_OUTPUT_TERMINAL: { usb_audio_output_term_descr_t *d = (usb_audio_output_term_descr_t *)descr; USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_units[%d] ---output term: type=0x%x sub=0x%x" " termid=0x%x\n\t" "termtype=0x%x assoc=0x%x sourceID=0x%x iterm=0x%x", d->bTerminalID, d->bDescriptorType, d->bDescriptorSubType, d->bTerminalID, d->wTerminalType, d->bAssocTerminal, d->bSourceID, d->iTerminal); usb_ac_alloc_unit(uacp, d->bTerminalID); uacp->usb_ac_units[d->bTerminalID].acu_descriptor = descr; uacp->usb_ac_units[d->bTerminalID].acu_type = buffer[2]; uacp->usb_ac_units[d->bTerminalID].acu_descr_length = len; break; } case USB_AUDIO_MIXER_UNIT: { usb_audio_mixer_unit_descr1_t *d = (usb_audio_mixer_unit_descr1_t *)descr; USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_units[%d] ---mixer unit: type=0x%x sub=0x%x" " unitid=0x%x\n\t" "#pins=0x%x sourceid[0]=0x%x", d->bUnitID, 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_SELECTOR_UNIT: { usb_audio_selector_unit_descr1_t *d = (usb_audio_selector_unit_descr1_t *)descr; USB_DPRINTF_L3(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "usb_ac_units[%d] ---selector unit: type=0x%x sub=0x%x" " unitid=0x%x\n\t" "#pins=0x%x sourceid[0]=0x%x", d->bUnitID, 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_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, "usb_ac_units[%d] ---feature unit: type=0x%x sub=0x%x" " unitid=0x%x\n\t" "sourceid=0x%x size=0x%x", d->bUnitID, 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, "usb_ac_units[%d] ---processing unit: type=0x%x sub=0x%x" " unitid=0x%x\n\t" "#pins=0x%x sourceid[0]=0x%x", d->bUnitID, 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, "usb_ac_units[%d] ---mixer unit: type=0x%x sub=0x%x" " unitid=0x%x\n\t" "#pins=0x%x sourceid[0]=0x%x", d->bUnitID, 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; 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; } 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; /* * Looking for a input/ouput terminal type to match the port * type, it should not be common streaming type */ ASSERT(termtype != USB_AUDIO_TERM_TYPE_STREAMING); 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 (USB_PORT_UNKNOWN); } /* * 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 & USB_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 wTerminalType=0x%x, name=%s", dir, d->wTerminalType, usb_audio_dtypes[port_type]); uacp->usb_ac_output_ports |= (1U << port_type); } else { usb_audio_input_term_descr_t *d = (usb_audio_input_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 wTerminalType=0x%x, name=%s", dir, d->wTerminalType, usb_audio_dtypes[port_type]); uacp->usb_ac_input_ports |= (1U << port_type); } 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 & USB_AUDIO_PLAY) ? USB_AUDIO_OUTPUT_TERMINAL : USB_AUDIO_INPUT_TERMINAL; (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; /* we only support the selector for the record side */ if (dir & USB_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; if (dir & USB_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 (((1U << 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 & USB_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; /* * 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(1, BPRI_HI); if (!data) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_set_selector: allocate data failed"); mutex_enter(&uacp->usb_ac_mutex); return (USB_FAILURE); } /* 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; 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; start_type = (dir & USB_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 & USB_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; 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) { uacp->usb_ac_traverse_path[uacp->usb_ac_traverse_path_index++] = (uchar_t)unit; ASSERT(uacp->usb_ac_traverse_path_index < uacp->usb_ac_max_unit); } /* ARGSUSED */ static void usb_ac_pop_unit_id(usb_ac_state_t *uacp, uint_t unit) { 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 & USB_AUDIO_PLAY) ? USB_AUDIO_OUTPUT_TERMINAL : USB_AUDIO_INPUT_TERMINAL; /* 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 & USB_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:start"); usb_ac_serialize_access(uacp); mutex_enter(&uacp->usb_ac_mutex); /* setting to disconnect state will prevent replumbing */ 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_ac_release_access(uacp); USB_DPRINTF_L4(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle, "usb_ac_disconnect_event_cb:done"); 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:begain"); 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); USB_DPRINTF_L4(PRINT_MASK_EVENTS, uacp->usb_ac_log_handle, "usb_ac_reconnect_event_cb:done"); 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); usb_restore_engine(uacp); 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)); 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(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { int rval = USB_SUCCESS; mutex_enter(&uacp->usb_ac_mutex); if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) { mutex_exit(&uacp->usb_ac_mutex); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_serialize_access(uacp); rval = usb_ac_do_setup(uacp, engine); usb_ac_release_access(uacp); return (rval); } /* * 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(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { usb_ac_streams_info_t *streams_infop = NULL; mutex_enter(&uacp->usb_ac_mutex); streams_infop = (usb_ac_streams_info_t *)engine->streams; /* * 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, engine, 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--; mutex_exit(&uacp->usb_ac_mutex); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); 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(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { usb_ac_streams_info_t *streams_infop = NULL; usb_ac_serialize_access(uacp); streams_infop = engine->streams; mutex_enter(&uacp->usb_ac_mutex); /* 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_teardown: more than one setup/teardown, " "cnt=%d", streams_infop->acs_setup_teardown_count); goto done; } /* Send teardown command to usb_as */ if (usb_ac_send_as_cmd(uacp, engine, USB_AUDIO_TEARDOWN, (void *)NULL) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_teardown: failure"); streams_infop->acs_setup_teardown_count++; goto done; } done: mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_teardown: End"); usb_ac_release_access(uacp); } /* * 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: "); /* 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 (usb_ac_get_maxmin_volume(uacp, channel, USB_AUDIO_GET_MAX, dir, featureID, &max) != USB_SUCCESS) { 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 (usb_ac_get_maxmin_volume(uacp, channel, USB_AUDIO_GET_MIN, dir, featureID, &min) != USB_SUCCESS) { 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 (usb_ac_get_maxmin_volume(uacp, channel, USB_AUDIO_GET_CUR, dir, featureID, ¤t) != USB_SUCCESS) { 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) * (AF_MAX_GAIN - gain))/AF_MAX_GAIN; } 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 (usb_ac_get_maxmin_volume(uacp, channel, USB_AUDIO_GET_CUR, dir, featureID, ¤t) != USB_SUCCESS) { 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(). */ int usb_ac_set_format(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { usb_ac_streams_info_t *streams_infop = NULL; usb_audio_formats_t format; int old_setup_teardown_count = 0; mutex_enter(&uacp->usb_ac_mutex); streams_infop = (usb_ac_streams_info_t *)engine->streams; if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) { mutex_exit(&uacp->usb_ac_mutex); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_serialize_access(uacp); mutex_enter(&uacp->usb_ac_mutex); bzero(&format, sizeof (usb_audio_formats_t)); /* save format info */ format.fmt_n_srs = 1; format.fmt_srs = (uint_t *)&(engine->fmt.sr); format.fmt_chns = (uchar_t)engine->fmt.ch; format.fmt_precision = (uchar_t)engine->fmt.prec; format.fmt_encoding = (uchar_t)engine->fmt.enc; old_setup_teardown_count = streams_infop->acs_setup_teardown_count; /* isoc pipe not open and playing is not in progress */ if (old_setup_teardown_count) { streams_infop->acs_setup_teardown_count = 1; mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); usb_ac_stop_play(uacp, engine); usb_ac_teardown(uacp, engine); usb_ac_serialize_access(uacp); mutex_enter(&uacp->usb_ac_mutex); } /* * 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, engine, USB_AUDIO_SET_FORMAT, &format) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_set_format: failed"); goto fail; } int sample = engine->fmt.sr; /* Set the sample rate */ if (usb_ac_send_as_cmd(uacp, engine, USB_AUDIO_SET_SAMPLE_FREQ, &sample) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_set_format: setting format failed"); goto fail; } mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); /* This should block until successful */ if (old_setup_teardown_count) { (void) usb_ac_setup(uacp, engine); } mutex_enter(&uacp->usb_ac_mutex); streams_infop->acs_setup_teardown_count = old_setup_teardown_count; mutex_exit(&uacp->usb_ac_mutex); return (USB_SUCCESS); fail: streams_infop->acs_setup_teardown_count = old_setup_teardown_count; mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); return (USB_FAILURE); } /* * 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(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { int samples; usb_audio_play_req_t play_req; mutex_enter(&uacp->usb_ac_mutex); if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) { mutex_exit(&uacp->usb_ac_mutex); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_serialize_access(uacp); mutex_enter(&uacp->usb_ac_mutex); /* Check for continuous sample rate done in usb_as */ samples = engine->fmt.sr * engine->fmt.ch / engine->intrate; if (samples & engine->fmt.ch) { samples++; } play_req.up_samples = samples; play_req.up_handle = uacp; /* Send setup command to usb_as */ if (usb_ac_send_as_cmd(uacp, engine, 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"); mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); return (USB_SUCCESS); } /* * usb_ac_stop_play: * Stop the play engine * called from mixer framework. */ void usb_ac_stop_play(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { if (engine == NULL) { engine = &(uacp->engines[0]); } 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); mutex_enter(&uacp->usb_ac_mutex); /* Send setup command to usb_as */ if (usb_ac_send_as_cmd(uacp, engine, 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"); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); } /* * 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(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { mutex_enter(&uacp->usb_ac_mutex); if (uacp->usb_ac_dev_state != USB_DEV_ONLINE) { mutex_exit(&uacp->usb_ac_mutex); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_serialize_access(uacp); mutex_enter(&uacp->usb_ac_mutex); /* Send setup command to usb_as */ if (usb_ac_send_as_cmd(uacp, engine, USB_AUDIO_START_RECORD, (void *)uacp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_start_record: failure"); mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); return (USB_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(usb_ac_state_t *uacp, usb_audio_eng_t *engine) { usb_ac_serialize_access(uacp); mutex_enter(&uacp->usb_ac_mutex); /* Send setup command to usb_as */ if (usb_ac_send_as_cmd(uacp, engine, USB_AUDIO_STOP_RECORD, NULL) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_do_stop_record: failure"); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_release_access(uacp); } /* * 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 */ /* ARGSUSED */ static int usb_ac_get_maxmin_volume(usb_ac_state_t *uacp, uint_t channel, int cmd, int dir, int feature_unitID, short *max_or_minp) { mblk_t *data = NULL; usb_cr_t cr; usb_cb_flags_t cb_flags; 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(MBLKL(data) == 2); *max_or_minp = (*(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_minp); freemsg(data); return (USB_SUCCESS); } /* * usb_ac_set_volume: * Send USBA command down to set the gain balance */ /* ARGSUSED */ 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; mutex_exit(&uacp->usb_ac_mutex); /* Construct the mblk_t from gain for sending to USBA */ data = allocb(4, BPRI_HI); if (!data) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_set_volume: allocate data failed"); mutex_enter(&uacp->usb_ac_mutex); return (USB_FAILURE); } *(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 */ 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; 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(1, BPRI_HI); if (!data) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_set_mute: allocate data failed"); mutex_enter(&uacp->usb_ac_mutex); return (USB_FAILURE); } *(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_audio_eng_t *engine, int cmd, void *arg) { usb_ac_streams_info_t *streams_infop; usb_ac_plumbed_t *plumb_infop; int rv; int rval; ldi_handle_t lh; ASSERT(mutex_owned(&uacp->usb_ac_mutex)); streams_infop = engine->streams; plumb_infop = streams_infop->acs_plumbed; lh = plumb_infop->acp_lh; rv = ldi_ioctl(lh, cmd, (intptr_t)arg, FKIOCTL, kcred, &rval); if (rv != 0) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_send_as_cmd: ldi_ioctl failed, error=%d", rv); return (USB_FAILURE); } return (USB_SUCCESS); } /* * 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); } } } /* * handle read from plumbed drivers */ static void usb_ac_reader(void *argp) { usb_ac_plumbed_t *acp = (usb_ac_plumbed_t *)argp; usb_ac_state_t *uacp = acp->acp_uacp; ldi_handle_t lh; mblk_t *mp; int rv; timestruc_t tv = {0}; mutex_enter(&uacp->usb_ac_mutex); lh = acp->acp_lh; tv.tv_sec = usb_ac_wait_hid; while (acp->acp_flags & ACP_ENABLED) { mp = NULL; mutex_exit(&uacp->usb_ac_mutex); rv = ldi_getmsg(lh, &mp, &tv); mutex_enter(&uacp->usb_ac_mutex); if (rv == ENODEV) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "Device is not availabe"); break; } if ((acp->acp_flags & ACP_ENABLED) && mp != NULL && rv == 0) rv = usb_ac_read_msg(acp, mp); } mutex_exit(&uacp->usb_ac_mutex); } /* * setup threads to read from the other usb modules that may send unsolicited * or asynchronous messages, which is only hid currently */ static int usb_ac_plumb(usb_ac_plumbed_t *acp) { usb_ac_state_t *uacp = acp->acp_uacp; dev_info_t *dip; dev_info_t *acp_dip; int acp_inst; char *acp_name; char tq_nm[128]; int rv = USB_FAILURE; mutex_enter(&uacp->usb_ac_mutex); dip = uacp->usb_ac_dip; acp_dip = acp->acp_dip; acp_inst = ddi_get_instance(acp_dip); acp_name = (char *)ddi_driver_name(acp_dip); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_plumb:begin"); if (strcmp(acp_name, "hid") != 0) { rv = USB_SUCCESS; goto OUT; } (void) snprintf(tq_nm, sizeof (tq_nm), "%s_%d_tq", ddi_driver_name(acp_dip), acp_inst); acp->acp_tqp = ddi_taskq_create(dip, tq_nm, 1, TASKQ_DEFAULTPRI, 0); if (acp->acp_tqp == NULL) goto OUT; if (ddi_taskq_dispatch(acp->acp_tqp, usb_ac_reader, (void *)acp, DDI_SLEEP) != DDI_SUCCESS) goto OUT; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_plumb: dispatched reader"); rv = USB_SUCCESS; OUT: mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_plumb: done, rv=%d", rv); return (rv); } static void usb_ac_mux_plumbing_tq(void *arg) { usb_ac_state_t *uacp = (usb_ac_state_t *)arg; if (usb_ac_mux_plumbing(uacp) != USB_SUCCESS) USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing_tq:failed"); } static int usb_ac_do_plumbing(usb_ac_state_t *uacp) { dev_info_t *dip = uacp->usb_ac_dip; int inst = ddi_get_instance(dip); char tq_nm[128]; int rv = USB_FAILURE; (void) snprintf(tq_nm, sizeof (tq_nm), "%s_%d_tq", ddi_driver_name(dip), inst); uacp->tqp = ddi_taskq_create(dip, tq_nm, 1, TASKQ_DEFAULTPRI, 0); if (uacp->tqp == NULL) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_do_plumbing: ddi_taskq_create failed"); goto OUT; } if (ddi_taskq_dispatch(uacp->tqp, usb_ac_mux_plumbing_tq, (void *)uacp, DDI_SLEEP) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_do_plumbing: ddi_taskq_dispatch failed"); goto OUT; } rv = USB_SUCCESS; OUT: return (rv); } static void usb_ac_mux_unplumbing_tq(void *arg) { usb_ac_state_t *uacp = (usb_ac_state_t *)arg; if (usb_ac_mux_unplumbing(uacp) != USB_SUCCESS) USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing:failed"); } static int usb_ac_do_unplumbing(usb_ac_state_t *uacp) { int rv = USB_FAILURE; if (uacp->tqp == NULL) return (USB_SUCCESS); if (ddi_taskq_dispatch(uacp->tqp, usb_ac_mux_unplumbing_tq, (void *)uacp, DDI_SLEEP) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_do_unplumbing: ddi_taskq_dispatch failed"); goto OUT; } USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_do_unplumbing: waiting for unplumb thread"); ddi_taskq_wait(uacp->tqp); rv = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_do_unplumbing: unplumb thread done"); OUT: if (uacp->tqp != NULL) { ddi_taskq_destroy(uacp->tqp); uacp->tqp = NULL; } return (rv); } /* * teardown threads to the other usb modules * and clear structures as part of unplumbing */ static void usb_ac_unplumb(usb_ac_plumbed_t *acp) { usb_ac_streams_info_t *streams_infop; usb_ac_state_t *uacp = acp->acp_uacp; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_unplumb: begin"); if (acp->acp_tqp != NULL) { USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_unplumb: destroying taskq"); ddi_taskq_destroy(acp->acp_tqp); } mutex_enter(&uacp->usb_ac_mutex); if (acp->acp_driver == USB_AS_PLUMBED) { /* * we bzero the streams info and plumbed structure * since there is no guarantee that the next plumbing * will be identical */ streams_infop = (usb_ac_streams_info_t *)acp->acp_data; /* bzero the relevant plumbing structure */ bzero(streams_infop, sizeof (usb_ac_streams_info_t)); } bzero(acp, sizeof (usb_ac_plumbed_t)); mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_unplumb: done"); } /*ARGSUSED*/ static int usb_ac_mux_plumbing(usb_ac_state_t *uacp) { dev_info_t *dip; /* get the usb_ac dip */ dip = uacp->usb_ac_dip; /* Access to the global variables is synchronized */ mutex_enter(&uacp->usb_ac_mutex); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing:state = %d", uacp->usb_ac_plumbing_state); if (uacp->usb_ac_plumbing_state >= USB_AC_STATE_PLUMBED) { mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing: audio streams driver" " already plumbed"); return (USB_SUCCESS); } /* usb_as and hid should be attached but double check */ if (usb_ac_online_siblings(uacp) != USB_SUCCESS) { mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing:no audio streams driver plumbed"); return (USB_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing: raising power"); mutex_exit(&uacp->usb_ac_mutex); /* bring the device to full power */ usb_ac_pm_busy_component(uacp); (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); /* avoid dips disappearing while we are plumbing */ usb_ac_hold_siblings(uacp); mutex_enter(&uacp->usb_ac_mutex); /* * walk all siblings and create the usb_ac<->usb_as and * usb_ac<->hid streams. return of 0 indicates no or * partial/failed plumbing */ if (usb_ac_mux_walk_siblings(uacp) == 0) { /* pretend that we are plumbed so we can unplumb */ uacp->usb_ac_plumbing_state = USB_AC_STATE_PLUMBED; mutex_exit(&uacp->usb_ac_mutex); (void) usb_ac_mux_unplumbing(uacp); USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing: no audio streams driver plumbed"); usb_ac_rele_siblings(uacp); usb_ac_pm_idle_component(uacp); return (USB_FAILURE); } uacp->usb_ac_plumbing_state = USB_AC_STATE_PLUMBED; /* restore state if we have already registered with the mixer */ if (uacp->usb_ac_registered_with_mixer) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing:already registered with mixer," "restoring state"); (void) usb_ac_restore_audio_state(uacp, USB_FLAGS_SLEEP); } else if (usb_ac_mixer_registration(uacp) != USB_SUCCESS) { mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing: mixer registration failed"); (void) usb_ac_mux_unplumbing(uacp); usb_ac_rele_siblings(uacp); usb_ac_pm_idle_component(uacp); return (USB_FAILURE); } mutex_exit(&uacp->usb_ac_mutex); usb_ac_rele_siblings(uacp); usb_ac_pm_idle_component(uacp); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_plumbing:done"); return (USB_SUCCESS); } static int usb_ac_mux_unplumbing(usb_ac_state_t *uacp) { usb_ac_plumbed_t *acp; ldi_handle_t lh; dev_info_t *acp_dip; int inst; int i; dev_t devt; minor_t minor; int maxlinked = 0; mutex_enter(&uacp->usb_ac_mutex); if (uacp->usb_ac_plumbing_state == USB_AC_STATE_UNPLUMBED) { USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: already unplumbed!"); mutex_exit(&uacp->usb_ac_mutex); return (USB_SUCCESS); } /* usb_ac might not have anything plumbed yet */ if (uacp->usb_ac_current_plumbed_index == -1) { USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: nothing plumbed"); uacp->usb_ac_plumbing_state = USB_AC_STATE_UNPLUMBED; mutex_exit(&uacp->usb_ac_mutex); return (USB_SUCCESS); } /* do not allow detach if still busy */ if (uacp->usb_ac_busy_count) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: mux still busy (%d)", uacp->usb_ac_busy_count); mutex_exit(&uacp->usb_ac_mutex); return (USB_FAILURE); } uacp->usb_ac_plumbing_state = USB_AC_STATE_UNPLUMBED; /* close ac-as and ac-hid streams */ maxlinked = uacp->usb_ac_current_plumbed_index + 1; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: maxlinked = %d", maxlinked); for (i = 0; i < maxlinked; i++) { /* * we must save members of usb_ac_plumbed[] before calling * usb_ac_unplumb() because it clears the structure */ acp = &uacp->usb_ac_plumbed[i]; lh = acp->acp_lh; acp_dip = acp->acp_dip; devt = acp->acp_devt; if (acp_dip == NULL) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: [%d] - skipping", i); continue; } minor = getminor(devt); inst = ddi_get_instance(acp_dip); uacp->usb_ac_current_plumbed_index = i; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: [%d] - %s%d minor 0x%x", i, ddi_driver_name(acp_dip), inst, minor); if (lh != NULL) { acp->acp_flags &= ~ACP_ENABLED; mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing:[%d] - closing", i); /* * ldi_close will cause panic if ldi_getmsg * is not finished. ddi_taskq_destroy will wait * for the thread to complete. */ usb_ac_unplumb(acp); (void) ldi_close(lh, FREAD|FWRITE, kcred); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: [%d] - unplumbed", i); mutex_enter(&uacp->usb_ac_mutex); } } mutex_exit(&uacp->usb_ac_mutex); /* Wait till all activity in the default pipe has drained */ usb_ac_serialize_access(uacp); usb_ac_release_access(uacp); mutex_enter(&uacp->usb_ac_mutex); uacp->usb_ac_current_plumbed_index = -1; mutex_exit(&uacp->usb_ac_mutex); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_unplumbing: done"); return (USB_SUCCESS); } /* * walk all siblings and create the ac<->as and ac<->hid streams */ static int usb_ac_mux_walk_siblings(usb_ac_state_t *uacp) { dev_info_t *pdip; dev_info_t *child_dip; major_t drv_major; minor_t drv_minor; int drv_instance; char *drv_name; dev_t drv_devt; ldi_handle_t drv_lh; ldi_ident_t li; int error; int count = 0; ASSERT(mutex_owned(&uacp->usb_ac_mutex)); pdip = ddi_get_parent(uacp->usb_ac_dip); child_dip = ddi_get_child(pdip); while ((child_dip != NULL) && (count < USB_AC_MAX_PLUMBED)) { drv_instance = ddi_get_instance(child_dip); drv_name = (char *)ddi_driver_name(child_dip); USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings: plumbing %s%d count=%d", drv_name, drv_instance, count); /* ignore own dip */ if (child_dip == uacp->usb_ac_dip) { child_dip = ddi_get_next_sibling(child_dip); continue; } drv_instance = ddi_get_instance(child_dip); /* ignore other dip other than usb_as and hid */ if (strcmp(ddi_driver_name(child_dip), "usb_as") == 0) { uacp->usb_ac_plumbed[count].acp_driver = USB_AS_PLUMBED; drv_minor = USB_AS_CONSTRUCT_MINOR(drv_instance); } else if (strcmp(ddi_driver_name(child_dip), "hid") == 0) { uacp->usb_ac_plumbed[count].acp_driver = USB_AH_PLUMBED; drv_minor = HID_CONSTRUCT_EXTERNAL_MINOR(drv_instance); } else { drv_minor = drv_instance; uacp->usb_ac_plumbed[count].acp_driver = UNKNOWN_PLUMBED; child_dip = ddi_get_next_sibling(child_dip); continue; } if (!i_ddi_devi_attached(child_dip)) { child_dip = ddi_get_next_sibling(child_dip); continue; } if (DEVI_IS_DEVICE_REMOVED(child_dip)) { child_dip = ddi_get_next_sibling(child_dip); continue; } drv_major = ddi_driver_major(child_dip); uacp->usb_ac_current_plumbed_index = count; mutex_exit(&uacp->usb_ac_mutex); drv_devt = makedevice(drv_major, drv_minor); USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings:: opening %s%d devt=(%d, 0x%x)", drv_name, drv_instance, drv_major, drv_minor); error = ldi_ident_from_dip(uacp->usb_ac_dip, &li); if (error == 0) { mutex_enter(&uacp->usb_ac_mutex); uacp->usb_ac_plumbed[count].acp_flags |= ACP_ENABLED; mutex_exit(&uacp->usb_ac_mutex); error = ldi_open_by_dev(&drv_devt, OTYP_CHR, FREAD|FWRITE, kcred, &drv_lh, li); ldi_ident_release(li); } mutex_enter(&uacp->usb_ac_mutex); if (error) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings: open of devt=(%d, 0x%x)" " failed error=%d", drv_major, drv_minor, error); return (0); } uacp->usb_ac_plumbed[count].acp_uacp = uacp; uacp->usb_ac_plumbed[count].acp_devt = drv_devt; uacp->usb_ac_plumbed[count].acp_lh = drv_lh; uacp->usb_ac_plumbed[count].acp_dip = child_dip; uacp->usb_ac_plumbed[count].acp_ifno = usb_get_if_number(child_dip); if (uacp->usb_ac_plumbed[count].acp_driver == USB_AS_PLUMBED) { /* get registration data */ if (usb_ac_get_reg_data(uacp, drv_lh, count) != USB_SUCCESS) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings:" "usb_ac_get_reg_data failed on %s%d", drv_name, drv_instance); uacp->usb_ac_plumbed[count].acp_dip = NULL; return (0); } } else if (uacp->usb_ac_plumbed[count].acp_driver == USB_AH_PLUMBED) { int rval; USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings: pushing usb_ah on %s%d", drv_name, drv_instance); mutex_exit(&uacp->usb_ac_mutex); /* push usb_ah module on top of hid */ error = ldi_ioctl(drv_lh, I_PUSH, (intptr_t)"usb_ah", FKIOCTL, kcred, &rval); mutex_enter(&uacp->usb_ac_mutex); if (error) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings: ldi_ioctl" "I_PUSH failed on %s%d, error=%d", drv_name, drv_instance, error); uacp->usb_ac_plumbed[count].acp_dip = NULL; /* skip plumbing the hid driver */ child_dip = ddi_get_next_sibling(child_dip); continue; } } else { /* should not be here */ USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings:- unknown module %s%d", drv_name, drv_instance); count--; uacp->usb_ac_plumbed[count].acp_dip = NULL; /* skip plumbing an unknown module */ child_dip = ddi_get_next_sibling(child_dip); continue; } mutex_exit(&uacp->usb_ac_mutex); error = usb_ac_plumb(&uacp->usb_ac_plumbed[count]); mutex_enter(&uacp->usb_ac_mutex); if (error != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings: usb_ac_plumb " "failed for %s%d", drv_name, drv_instance); return (0); } USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings:plumbed %d, minor 0x%x", drv_instance, drv_minor); child_dip = ddi_get_next_sibling(child_dip); count++; } USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mux_walk_siblings: %d drivers plumbed under usb_ac mux", count); return (count); } /* * Register with mixer only after first plumbing. * Also do not register if earlier reg data * couldn't be received from at least one * streaming interface */ static int usb_ac_mixer_registration(usb_ac_state_t *uacp) { usb_as_registration_t *asreg; int n; if (uacp->usb_ac_registered_with_mixer) { return (USB_SUCCESS); } for (n = 0; n < USB_AC_MAX_AS_PLUMBED; n++) { if (uacp->usb_ac_streams[n].acs_rcvd_reg_data) { break; } } /* Haven't found a streaming interface; fail mixer registration */ if (n > USB_AC_MAX_AS_PLUMBED) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mixer_registration:- no streaming interface found"); return (USB_FAILURE); } /* * Fill out streaming interface specific stuff * Note that we handle only one playing and one recording * streaming interface at the most */ for (n = 0; n < USB_AC_MAX_AS_PLUMBED; n++) { int ch, chs, id; if (uacp->usb_ac_streams[n].acs_rcvd_reg_data == 0) { continue; } asreg = &(uacp->usb_ac_streams[n].acs_streams_reg); if (asreg->reg_valid == 0) { continue; } chs = asreg->reg_formats[0].fmt_chns; /* check if any channel supports vol. control for this fmt */ for (ch = 0; ch <= chs; ch++) { if ((id = usb_ac_get_featureID(uacp, asreg->reg_mode, ch, USB_AUDIO_VOLUME_CONTROL)) != -1) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mixer_registration:n= [%d]" "- dir=%d featureID=%d", n, asreg->reg_mode, id); break; } } uacp->usb_ac_streams[n].acs_default_gain = (id == USB_AC_ID_NONE) ? (AF_MAX_GAIN): (AF_MAX_GAIN*3/4); USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mixer_registration:n= [%d] - mode=%d chs=%d" "default_gain=%d id=%d", n, asreg->reg_mode, chs, uacp->usb_ac_streams[n].acs_default_gain, id); } /* the rest */ USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mixer_registration: calling usb_audio_register"); mutex_exit(&uacp->usb_ac_mutex); if (usb_audio_register(uacp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_mixer_registration: usb_audio_register failed"); mutex_enter(&uacp->usb_ac_mutex); return (USB_FAILURE); } mutex_enter(&uacp->usb_ac_mutex); uacp->usb_ac_registered_with_mixer = 1; return (USB_SUCCESS); } /* * Get registriations data when driver attach */ static int usb_ac_get_reg_data(usb_ac_state_t *uacp, ldi_handle_t drv_lh, int index) { int n, error, rval; usb_as_registration_t *streams_reg; ASSERT(uacp->usb_ac_registered_with_mixer == 0); for (n = 0; n < USB_AC_MAX_AS_PLUMBED; n++) { /* * We haven't received registration data * from n-th streaming interface in the array */ if (!uacp->usb_ac_streams[n].acs_rcvd_reg_data) { break; } } if (n >= USB_AC_MAX_AS_PLUMBED) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "More than 2 streaming interfaces (play " "and/or record) currently not supported"); return (USB_FAILURE); } /* take the stream reg struct with the same index */ streams_reg = &uacp->usb_ac_streams[n].acs_streams_reg; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_get_reg_data:regdata from usb_as: streams_reg=0x%p, n=%d", (void *)streams_reg, n); mutex_exit(&uacp->usb_ac_mutex); if ((error = ldi_ioctl(drv_lh, USB_AUDIO_MIXER_REGISTRATION, (intptr_t)streams_reg, FKIOCTL, kcred, &rval)) != 0) { USB_DPRINTF_L2(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_get_reg_data: ldi_ioctl failed for" "mixer registration error=%d", error); mutex_enter(&uacp->usb_ac_mutex); return (USB_FAILURE); } else { mutex_enter(&uacp->usb_ac_mutex); rval = usb_ac_setup_plumbed(uacp, index, n); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_get_reg_data:usb_ac_streams[%d]: " "received_reg_data=%d type=%s", index, uacp->usb_ac_streams[n].acs_rcvd_reg_data, ((streams_reg->reg_mode == USB_AUDIO_PLAY) ? "play" : "record")); usb_ac_print_reg_data(uacp, streams_reg); return (rval); } } /* * setup plumbed and stream info structure */ static int usb_ac_setup_plumbed(usb_ac_state_t *uacp, int plb_idx, int str_idx) { uacp->usb_ac_plumbed[plb_idx].acp_data = &uacp->usb_ac_streams[str_idx]; uacp->usb_ac_streams[str_idx].acs_plumbed = &uacp->usb_ac_plumbed[plb_idx]; uacp->usb_ac_streams[str_idx].acs_rcvd_reg_data = 1; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_setup_plumbed: done - plb_idx=%d str_idx=%d ", plb_idx, str_idx); return (USB_SUCCESS); } /* * function to dump registration data */ static void usb_ac_print_reg_data(usb_ac_state_t *uacp, usb_as_registration_t *reg) { int n; for (n = 0; n < reg->reg_n_formats; n++) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "format%d: alt=%d chns=%d prec=%d enc=%d", n, reg->reg_formats[n].fmt_alt, reg->reg_formats[n].fmt_chns, reg->reg_formats[n].fmt_precision, reg->reg_formats[n].fmt_encoding); } for (n = 0; n < USB_AS_N_FORMATS; n++) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "reg_formats[%d] ptr=0x%p", n, (void *)®->reg_formats[n]); } USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_print_reg_data: End"); } static int usb_ac_online_siblings(usb_ac_state_t *uacp) { dev_info_t *pdip, *child_dip; int rval = USB_SUCCESS; ASSERT(mutex_owned(&uacp->usb_ac_mutex)); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_online_siblings:start"); pdip = ddi_get_parent(uacp->usb_ac_dip); child_dip = ddi_get_child(pdip); while (child_dip != NULL) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_online_siblings: onlining %s%d ref=%d", ddi_driver_name(child_dip), ddi_get_instance(child_dip), DEVI(child_dip)->devi_ref); /* Online the child_dip of usb_as and hid, if not already */ if ((strcmp(ddi_driver_name(child_dip), "usb_as") == 0) || (strcmp(ddi_driver_name(child_dip), "hid") == 0)) { mutex_exit(&uacp->usb_ac_mutex); if (ndi_devi_online(child_dip, NDI_ONLINE_ATTACH) != NDI_SUCCESS) { USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_online_siblings:failed to online" "device %s%d", ddi_driver_name(child_dip), ddi_get_instance(child_dip)); /* only onlining usb_as is fatal */ if (strcmp(ddi_driver_name(child_dip), "usb_as") == 0) { mutex_enter(&uacp->usb_ac_mutex); rval = USB_FAILURE; break; } } mutex_enter(&uacp->usb_ac_mutex); } child_dip = ddi_get_next_sibling(child_dip); } return (rval); } /* * hold all audio children before or after plumbing * online usb_as and hid, if not already */ static void usb_ac_hold_siblings(usb_ac_state_t *uacp) { dev_info_t *pdip, *child_dip; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_hold_siblings:start"); /* hold all siblings and ourselves */ pdip = ddi_get_parent(uacp->usb_ac_dip); /* hold the children */ ndi_devi_enter(pdip); child_dip = ddi_get_child(pdip); while (child_dip != NULL) { ndi_hold_devi(child_dip); USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_hold_siblings: held %s%d ref=%d", ddi_driver_name(child_dip), ddi_get_instance(child_dip), DEVI(child_dip)->devi_ref); child_dip = ddi_get_next_sibling(child_dip); } ndi_devi_exit(pdip); } /* * release all audio children before or after plumbing */ static void usb_ac_rele_siblings(usb_ac_state_t *uacp) { dev_info_t *pdip, *child_dip; USB_DPRINTF_L4(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_rele_siblings: start"); /* release all siblings and ourselves */ pdip = ddi_get_parent(uacp->usb_ac_dip); ndi_devi_enter(pdip); child_dip = ddi_get_child(pdip); while (child_dip != NULL) { ndi_rele_devi(child_dip); USB_DPRINTF_L3(PRINT_MASK_ALL, uacp->usb_ac_log_handle, "usb_ac_rele_siblings: released %s%d ref=%d", ddi_driver_name(child_dip), ddi_get_instance(child_dip), DEVI(child_dip)->devi_ref); child_dip = ddi_get_next_sibling(child_dip); } ndi_devi_exit(pdip); } static void usb_restore_engine(usb_ac_state_t *statep) { usb_audio_eng_t *engp; int i; for (i = 0; i < USB_AC_ENG_MAX; i++) { mutex_enter(&statep->usb_ac_mutex); engp = &statep->engines[i]; mutex_exit(&statep->usb_ac_mutex); if (engp->af_engp == NULL) continue; if (usb_ac_set_format(statep, engp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_restore_engine:set format fail, i=%d", i); return; } if (engp->started) { (void) usb_engine_start(engp); } } (void) usb_ac_ctrl_restore(statep); } /* * get the maximum format specification the device supports */ static void usb_ac_max_fmt(usb_as_registration_t *reg_data, usb_audio_format_t *fmtp) { uint_t ch = 0, sr = 0, prec = 0, enc = 0; int i; usb_audio_formats_t *reg_formats = reg_data->reg_formats; /* format priority: channels, sample rate, precision, encoding */ for (i = 0; i < reg_data->reg_n_formats; i++) { uint_t val, fmt_sr; int n, keep; val = reg_formats[i].fmt_chns; if (val < ch) continue; if (val > ch) keep = 1; for (n = 0, fmt_sr = 0; n < reg_formats[i].fmt_n_srs; n++) { if (fmt_sr < reg_formats[i].fmt_srs[n]) { fmt_sr = reg_formats[i].fmt_srs[n]; } } if (!keep && fmt_sr < sr) continue; if (fmt_sr > sr) keep = 1; val = reg_formats[i].fmt_precision; if (!keep && (val < prec)) continue; if (val > prec) keep = 1; val = reg_formats[i].fmt_encoding; if (!keep && (val < enc)) continue; ch = reg_formats[i].fmt_chns; sr = fmt_sr; prec = reg_formats[i].fmt_precision; enc = reg_formats[i].fmt_encoding; } fmtp->ch = ch; fmtp->sr = sr; fmtp->prec = prec; fmtp->enc = enc; } static void usb_ac_rem_eng(usb_ac_state_t *statep, usb_audio_eng_t *engp) { if (statep->usb_ac_audio_dev == NULL || engp->af_engp == NULL) return; audio_dev_remove_engine(statep->usb_ac_audio_dev, engp->af_engp); audio_engine_free(engp->af_engp); mutex_enter(&engp->lock); engp->af_engp = NULL; engp->streams = NULL; mutex_exit(&engp->lock); mutex_destroy(&engp->lock); cv_destroy(&engp->usb_audio_cv); } static int usb_ac_add_eng(usb_ac_state_t *uacp, usb_ac_streams_info_t *asinfo) { audio_dev_t *af_devp = uacp->usb_ac_audio_dev; usb_audio_eng_t *engp; audio_engine_t *af_engp; int rv = USB_FAILURE; int dir = asinfo->acs_streams_reg.reg_mode; uint_t defgain; if (asinfo->acs_rcvd_reg_data == 0) { return (USB_SUCCESS); } if (dir == USB_AUDIO_PLAY) { engp = &(uacp->engines[0]); } else { engp = &(uacp->engines[1]); } cv_init(&engp->usb_audio_cv, NULL, CV_DRIVER, NULL); mutex_init(&engp->lock, NULL, MUTEX_DRIVER, NULL); mutex_enter(&engp->lock); engp->af_eflags = (dir == USB_AUDIO_PLAY)?ENGINE_OUTPUT_CAP:ENGINE_INPUT_CAP; engp->statep = uacp; /* Set the format for the engine */ usb_ac_max_fmt(&(asinfo->acs_streams_reg), &engp->fmt); /* init the default gain */ defgain = asinfo->acs_default_gain; if (engp->fmt.ch == 2) { engp->af_defgain = AUDIO_CTRL_STEREO_VAL(defgain, defgain); } else { engp->af_defgain = defgain; } engp->streams = asinfo; mutex_exit(&engp->lock); af_engp = audio_engine_alloc(&usb_engine_ops, engp->af_eflags); if (af_engp == NULL) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "audio_engine_alloc failed"); goto OUT; } ASSERT(engp->af_engp == 0); mutex_enter(&engp->lock); engp->af_engp = af_engp; mutex_exit(&engp->lock); audio_engine_set_private(af_engp, engp); audio_dev_add_engine(af_devp, af_engp); /* * Set the format for this engine */ if (usb_ac_set_format(uacp, engp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uacp->usb_ac_log_handle, "set format failed, dir = %d", dir); goto OUT; } rv = USB_SUCCESS; OUT: if (rv != USB_SUCCESS) usb_ac_rem_eng(uacp, engp); return (rv); } static int usb_ac_ctrl_set_defaults(usb_ac_state_t *statep) { usb_audio_ctrl_t *ctrlp; int rv = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_ac_ctrl_set_defaults:begin"); for (int i = 0; i < CTL_NUM; i++) { ctrlp = statep->controls[i]; if (!ctrlp) { continue; } if (audio_control_write(ctrlp->af_ctrlp, ctrlp->cval)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_ac_ctrl_set_defaults:control write failed"); rv = USB_FAILURE; } } USB_DPRINTF_L4(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_ac_ctrl_set_defaults:end"); return (rv); } static int usb_ac_ctrl_restore(usb_ac_state_t *statep) { usb_audio_ctrl_t *ctrlp; int rv = USB_SUCCESS; for (int i = 0; i < CTL_NUM; i++) { ctrlp = statep->controls[i]; if (ctrlp) { USB_DPRINTF_L3(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_ac_ctrl_restore:i = %d", i); if (audio_control_write(ctrlp->af_ctrlp, ctrlp->cval)) { rv = USB_FAILURE; } } } return (rv); } /* * moves data between driver buffer and framework/shim buffer */ static void usb_eng_bufio(usb_audio_eng_t *engp, void *buf, size_t sz) { size_t cpsz = sz; caddr_t *src, *dst; if (engp->af_eflags & ENGINE_OUTPUT_CAP) { src = &engp->bufpos; dst = (caddr_t *)&buf; } else { src = (caddr_t *)&buf; dst = &engp->bufpos; } /* * Wrap. If sz is exactly the remainder of the buffer * (bufpos + sz == bufendp) then the second cpsz should be 0 and so * the second memcpy() should have no effect, with bufpos updated * to the head of the buffer. */ if (engp->bufpos + sz >= engp->bufendp) { cpsz = (size_t)engp->bufendp - (size_t)engp->bufpos; (void) memcpy(*dst, *src, cpsz); buf = (caddr_t)buf + cpsz; engp->bufpos = engp->bufp; cpsz = sz - cpsz; } if (cpsz) { (void) memcpy(*dst, *src, cpsz); engp->bufpos += cpsz; } engp->bufio_count++; } /* * control read callback */ static int usb_audio_ctrl_read(void *arg, uint64_t *cvalp) { usb_audio_ctrl_t *ctrlp = arg; mutex_enter(&ctrlp->ctrl_mutex); *cvalp = ctrlp->cval; mutex_exit(&ctrlp->ctrl_mutex); return (0); } /* * stereo level control callback */ static int usb_audio_write_stero_rec(void *arg, uint64_t cval) { usb_audio_ctrl_t *ctrlp = arg; usb_ac_state_t *statep = ctrlp->statep; int rv = EIO; int left, right; uint_t count = 0; left = AUDIO_CTRL_STEREO_LEFT(cval); right = AUDIO_CTRL_STEREO_RIGHT(cval); if (left < AF_MIN_GAIN || left > AF_MAX_GAIN || right < AF_MIN_GAIN || right > AF_MAX_GAIN) { return (EINVAL); } mutex_enter(&ctrlp->ctrl_mutex); ctrlp->cval = cval; mutex_exit(&ctrlp->ctrl_mutex); mutex_enter(&statep->usb_ac_mutex); (void) usb_ac_set_control(statep, USB_AUDIO_RECORD, USB_AUDIO_FEATURE_UNIT, 1, USB_AUDIO_VOLUME_CONTROL, USB_AC_FIND_ALL, &count, left, usb_ac_set_gain); (void) usb_ac_set_control(statep, USB_AUDIO_RECORD, USB_AUDIO_FEATURE_UNIT, 2, USB_AUDIO_VOLUME_CONTROL, USB_AC_FIND_ALL, &count, right, usb_ac_set_gain); rv = 0; done: mutex_exit(&statep->usb_ac_mutex); return (rv); } static int usb_audio_write_ster_vol(void *arg, uint64_t cval) { usb_audio_ctrl_t *ctrlp = arg; usb_ac_state_t *statep = ctrlp->statep; int rv = EIO; int left, right; uint_t count = 0; left = AUDIO_CTRL_STEREO_LEFT(cval); right = AUDIO_CTRL_STEREO_RIGHT(cval); if (left < AF_MIN_GAIN || left > AF_MAX_GAIN || right < AF_MIN_GAIN || right > AF_MAX_GAIN) { return (EINVAL); } mutex_enter(&ctrlp->ctrl_mutex); ctrlp->cval = cval; mutex_exit(&ctrlp->ctrl_mutex); mutex_enter(&statep->usb_ac_mutex); (void) usb_ac_set_control(statep, USB_AUDIO_PLAY, USB_AUDIO_FEATURE_UNIT, 1, USB_AUDIO_VOLUME_CONTROL, USB_AC_FIND_ALL, &count, left, usb_ac_set_gain); (void) usb_ac_set_control(statep, USB_AUDIO_PLAY, USB_AUDIO_FEATURE_UNIT, 2, USB_AUDIO_VOLUME_CONTROL, USB_AC_FIND_ALL, &count, right, usb_ac_set_gain); rv = 0; OUT: mutex_exit(&statep->usb_ac_mutex); return (rv); } /* * mono level control callback */ static int usb_audio_write_mono_vol(void *arg, uint64_t cval) { usb_audio_ctrl_t *ctrlp = arg; usb_ac_state_t *statep = ctrlp->statep; int rv = EIO; int gain; uint_t count = 0; if (cval < (uint64_t)AF_MIN_GAIN || cval > (uint64_t)AF_MAX_GAIN) { return (EINVAL); } mutex_enter(&ctrlp->ctrl_mutex); ctrlp->cval = cval; mutex_exit(&ctrlp->ctrl_mutex); gain = (int)(cval); mutex_enter(&statep->usb_ac_mutex); (void) usb_ac_set_control(statep, USB_AUDIO_PLAY, USB_AUDIO_FEATURE_UNIT, 1, USB_AUDIO_VOLUME_CONTROL, USB_AC_FIND_ALL, &count, gain, usb_ac_set_gain); rv = 0; OUT: mutex_exit(&statep->usb_ac_mutex); return (rv); } /* * mono level control callback */ static int usb_audio_write_monitor_gain(void *arg, uint64_t cval) { usb_audio_ctrl_t *ctrlp = arg; usb_ac_state_t *statep = ctrlp->statep; int rv = EIO; int gain; uint_t count = 0; if (cval < (uint64_t)AF_MIN_GAIN || cval > (uint64_t)AF_MAX_GAIN) { return (EINVAL); } mutex_enter(&ctrlp->ctrl_mutex); ctrlp->cval = cval; mutex_exit(&ctrlp->ctrl_mutex); gain = (int)(cval); mutex_enter(&statep->usb_ac_mutex); (void) usb_ac_set_monitor_gain_control(statep, USB_AUDIO_RECORD, USB_AUDIO_INPUT_TERMINAL, 1, USB_AUDIO_VOLUME_CONTROL, USB_AC_FIND_ALL, &count, gain, usb_ac_set_monitor_gain); rv = 0; OUT: mutex_exit(&statep->usb_ac_mutex); return (rv); } static int usb_audio_write_mono_rec(void *arg, uint64_t cval) { usb_audio_ctrl_t *ctrlp = arg; usb_ac_state_t *statep = ctrlp->statep; int rv = EIO; int gain; uint_t count = 0; if (cval < (uint64_t)AF_MIN_GAIN || cval > (uint64_t)AF_MAX_GAIN) { return (EINVAL); } mutex_enter(&ctrlp->ctrl_mutex); ctrlp->cval = cval; mutex_exit(&ctrlp->ctrl_mutex); gain = (int)(cval); mutex_enter(&statep->usb_ac_mutex); (void) usb_ac_set_control(statep, USB_AUDIO_RECORD, USB_AUDIO_FEATURE_UNIT, 1, USB_AUDIO_VOLUME_CONTROL, USB_AC_FIND_ALL, &count, gain, usb_ac_set_gain); rv = 0; mutex_exit(&statep->usb_ac_mutex); return (rv); } static int usb_audio_write_mic_boost(void *arg, uint64_t cval) { usb_audio_ctrl_t *ctrlp = arg; mutex_enter(&ctrlp->ctrl_mutex); ctrlp->cval = cval; mutex_exit(&ctrlp->ctrl_mutex); /* do nothing here */ return (0); } static int usb_audio_write_rec_src(void *arg, uint64_t cval) { usb_audio_ctrl_t *ctrlp = arg; usb_ac_state_t *statep = ctrlp->statep; int rv = 0; if (cval & ~(statep->usb_ac_input_ports)) return (EINVAL); mutex_enter(&ctrlp->ctrl_mutex); ctrlp->cval = cval; mutex_exit(&ctrlp->ctrl_mutex); mutex_enter(&statep->usb_ac_mutex); if (usb_ac_set_port(statep, USB_AUDIO_RECORD, cval) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALL, statep->usb_ac_log_handle, "usb_audio_write_rec_src: failed"); rv = EINVAL; } mutex_exit(&statep->usb_ac_mutex); rv = 0; OUT: return (rv); } int usb_audio_set_mute(usb_ac_state_t *statep, uint64_t cval) { short muteval; int rval; uint_t count; muteval = (cval == 0) ? USB_AUDIO_MUTE_ON : USB_AUDIO_MUTE_OFF; count = 0; /* only support AUDIO_PLAY */ mutex_enter(&statep->usb_ac_mutex); (void) usb_ac_set_control(statep, USB_AUDIO_PLAY, USB_AUDIO_FEATURE_UNIT, 0, USB_AUDIO_MUTE_CONTROL, USB_AC_FIND_ALL, &count, muteval, usb_ac_set_mute); mutex_exit(&statep->usb_ac_mutex); rval = (count == 0) ? USB_SUCCESS : USB_FAILURE; return (rval); } /* * port selection control callback */ /* * audio control registration related routines */ static usb_audio_ctrl_t * usb_audio_ctrl_alloc(usb_ac_state_t *statep, uint32_t num, uint64_t val) { audio_ctrl_desc_t desc; audio_ctrl_wr_t fn; usb_audio_ctrl_t *pc; pc = kmem_zalloc(sizeof (usb_audio_ctrl_t), KM_SLEEP); mutex_init(&pc->ctrl_mutex, NULL, MUTEX_DRIVER, NULL); bzero(&desc, sizeof (desc)); switch (num) { case CTL_VOLUME_MONO: desc.acd_name = AUDIO_CTRL_ID_VOLUME; desc.acd_type = AUDIO_CTRL_TYPE_MONO; desc.acd_minvalue = 0; desc.acd_maxvalue = AF_MAX_GAIN; desc.acd_flags = AUDIO_CTRL_FLAG_MAINVOL | AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY | AUDIO_CTRL_FLAG_POLL; fn = usb_audio_write_mono_vol; break; case CTL_VOLUME_STERO: desc.acd_name = AUDIO_CTRL_ID_VOLUME; desc.acd_type = AUDIO_CTRL_TYPE_STEREO; desc.acd_minvalue = 0; desc.acd_maxvalue = AF_MAX_GAIN; desc.acd_flags = AUDIO_CTRL_FLAG_MAINVOL | AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY | AUDIO_CTRL_FLAG_POLL; fn = usb_audio_write_ster_vol; break; case CTL_REC_MONO: desc.acd_name = AUDIO_CTRL_ID_RECGAIN; desc.acd_type = AUDIO_CTRL_TYPE_MONO; desc.acd_minvalue = 0; desc.acd_maxvalue = AF_MAX_GAIN; desc.acd_flags = AUDIO_CTRL_FLAG_RECVOL|AUDIO_CTRL_FLAG_REC | AUDIO_CTRL_FLAG_RW; fn = usb_audio_write_mono_rec; break; case CTL_REC_STERO: desc.acd_name = AUDIO_CTRL_ID_RECGAIN; desc.acd_type = AUDIO_CTRL_TYPE_STEREO; desc.acd_minvalue = 0; desc.acd_maxvalue = AF_MAX_GAIN; desc.acd_flags = AUDIO_CTRL_FLAG_RECVOL|AUDIO_CTRL_FLAG_REC | AUDIO_CTRL_FLAG_RW; fn = usb_audio_write_stero_rec; break; case CTL_MONITOR_GAIN: desc.acd_name = AUDIO_CTRL_ID_MONGAIN; desc.acd_type = AUDIO_CTRL_TYPE_MONO; desc.acd_minvalue = 0; desc.acd_maxvalue = AF_MAX_GAIN; desc.acd_flags = AUDIO_CTRL_FLAG_MONVOL |AUDIO_CTRL_FLAG_MONITOR |AUDIO_CTRL_FLAG_RW; fn = usb_audio_write_monitor_gain; break; case CTL_MIC_BOOST: desc.acd_name = AUDIO_CTRL_ID_MICBOOST; desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN; desc.acd_minvalue = 0; desc.acd_maxvalue = 1; desc.acd_flags = AUDIO_CTRL_FLAG_RW; fn = usb_audio_write_mic_boost; break; case CTL_REC_SRC: desc.acd_name = AUDIO_CTRL_ID_RECSRC; desc.acd_type = AUDIO_CTRL_TYPE_ENUM; desc.acd_minvalue = statep->usb_ac_input_ports; desc.acd_maxvalue = statep->usb_ac_input_ports; desc.acd_flags = AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC; for (int i = 0; usb_audio_dtypes[i]; i++) { desc.acd_enum[i] = usb_audio_dtypes[i]; } fn = usb_audio_write_rec_src; break; default: break; } mutex_enter(&pc->ctrl_mutex); pc->statep = statep; pc->cval = val; pc->af_ctrlp = audio_dev_add_control(statep->usb_ac_audio_dev, &desc, usb_audio_ctrl_read, fn, pc); mutex_exit(&pc->ctrl_mutex); mutex_enter(&statep->usb_ac_mutex); statep->controls[num] = pc; mutex_exit(&statep->usb_ac_mutex); return (pc); } static void usb_audio_ctrl_free(usb_audio_ctrl_t *ctrlp) { kmem_free(ctrlp, sizeof (usb_audio_ctrl_t)); } static void usb_ac_rem_controls(usb_ac_state_t *statep) { usb_audio_ctrl_t *ctrlp; for (int i = 0; i < CTL_NUM; i++) { ctrlp = statep->controls[i]; if (ctrlp) { if (ctrlp->af_ctrlp != NULL) audio_dev_del_control(ctrlp->af_ctrlp); usb_audio_ctrl_free(ctrlp); mutex_enter(&statep->usb_ac_mutex); statep->controls[i] = NULL; mutex_exit(&statep->usb_ac_mutex); } } } static int usb_ac_add_controls(usb_ac_state_t *statep) { int rv = USB_FAILURE; usb_audio_format_t *format; if (statep->engines[0].af_engp) { /* Init controls for play format */ format = &(statep->engines[0].fmt); if (format->ch == 2) { (void) usb_audio_ctrl_alloc(statep, CTL_VOLUME_STERO, statep->engines[0].af_defgain); } else { (void) usb_audio_ctrl_alloc(statep, CTL_VOLUME_MONO, statep->engines[0].af_defgain); } } /* Init controls for rec format */ if (statep->engines[1].af_engp) { format = &(statep->engines[1].fmt); if (format->ch == 2) { (void) usb_audio_ctrl_alloc(statep, CTL_REC_STERO, statep->engines[1].af_defgain); } else { (void) usb_audio_ctrl_alloc(statep, CTL_REC_MONO, statep->engines[1].af_defgain); } /* Add monitor control */ { (void) usb_audio_ctrl_alloc(statep, CTL_MONITOR_GAIN, 0); } /* Add ports control */ { (void) usb_audio_ctrl_alloc(statep, CTL_REC_SRC, statep->usb_ac_input_ports); } } rv = USB_SUCCESS; OUT: if (rv != USB_SUCCESS) usb_ac_rem_controls(statep); return (rv); } /*ARGSUSED*/ static int usb_audio_unregister(usb_ac_state_t *statep) { int i; if (statep == NULL) return (USB_SUCCESS); if (statep->usb_ac_audio_dev == NULL) return (USB_SUCCESS); if ((statep->flags & AF_REGISTERED) && audio_dev_unregister(statep->usb_ac_audio_dev) != DDI_SUCCESS) { return (USB_FAILURE); } mutex_enter(&statep->usb_ac_mutex); statep->flags &= ~AF_REGISTERED; mutex_exit(&statep->usb_ac_mutex); for (i = 0; i < USB_AC_ENG_MAX; i++) usb_ac_rem_eng(statep, &statep->engines[i]); usb_ac_rem_controls(statep); audio_dev_free(statep->usb_ac_audio_dev); mutex_enter(&statep->usb_ac_mutex); statep->usb_ac_audio_dev = NULL; mutex_exit(&statep->usb_ac_mutex); return (USB_SUCCESS); } static int usb_audio_register(usb_ac_state_t *statep) { audio_dev_t *af_devp; int rv = USB_FAILURE; int n; af_devp = audio_dev_alloc(statep->usb_ac_dip, 0); audio_dev_set_description(af_devp, "USB Audio"); audio_dev_set_version(af_devp, "1.0"); mutex_enter(&statep->usb_ac_mutex); statep->usb_ac_audio_dev = af_devp; mutex_exit(&statep->usb_ac_mutex); for (n = 0; n < USB_AC_MAX_AS_PLUMBED; n++) { if (usb_ac_add_eng(statep, &(statep->usb_ac_streams[n])) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_audio_register: add engine n =%d failed", n); goto OUT; } } if (usb_ac_add_controls(statep) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_audio_register: add controls failed"); goto OUT; } if (usb_ac_ctrl_set_defaults(statep) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_audio_register: set defaults failed"); goto OUT; } if (audio_dev_register(af_devp) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "audio_dev_register() failed"); goto OUT; } mutex_enter(&statep->usb_ac_mutex); statep->flags |= AF_REGISTERED; mutex_exit(&statep->usb_ac_mutex); rv = USB_SUCCESS; OUT: if (rv != USB_SUCCESS) { (void) usb_audio_unregister(statep); } return (rv); } int usb_ac_get_audio(void *handle, void *buf, int samples) { usb_ac_state_t *statep = (usb_ac_state_t *)(handle); usb_audio_eng_t *engp = &(statep->engines[0]); unsigned reqframes = samples >> engp->frsmshift; unsigned frames; unsigned i; size_t sz; caddr_t bp = buf; mutex_enter(&engp->lock); if (!engp->started) { mutex_exit(&engp->lock); return (0); } engp->busy = B_TRUE; mutex_exit(&engp->lock); /* break requests from the driver into fragment sized chunks */ for (i = 0; i < reqframes; i += frames) { mutex_enter(&engp->lock); frames = reqframes - i; if (frames > engp->fragfr) frames = engp->fragfr; sz = (frames << engp->frsmshift) << engp->smszshift; /* must move data before updating framework */ usb_eng_bufio(engp, bp, sz); engp->frames += frames; bp += sz; mutex_exit(&engp->lock); } mutex_enter(&engp->lock); engp->io_count++; engp->busy = B_FALSE; cv_signal(&engp->usb_audio_cv); mutex_exit(&engp->lock); return (samples); } void usb_ac_send_audio(void *handle, void *buf, int samples) { usb_ac_state_t *statep = (usb_ac_state_t *)(handle); usb_audio_eng_t *engp = &(statep->engines[1]); unsigned reqframes = samples >> engp->frsmshift; unsigned frames; unsigned i; size_t sz; caddr_t bp = buf; mutex_enter(&engp->lock); if (!engp->started) { mutex_exit(&engp->lock); return; } engp->busy = B_TRUE; mutex_exit(&engp->lock); /* break requests from the driver into fragment sized chunks */ for (i = 0; i < reqframes; i += frames) { mutex_enter(&engp->lock); frames = reqframes - i; if (frames > engp->fragfr) frames = engp->fragfr; sz = (frames << engp->frsmshift) << engp->smszshift; /* must move data before updating framework */ usb_eng_bufio(engp, bp, sz); engp->frames += frames; bp += sz; mutex_exit(&engp->lock); } mutex_enter(&engp->lock); engp->io_count++; engp->busy = B_FALSE; cv_signal(&engp->usb_audio_cv); mutex_exit(&engp->lock); } /* * ************************************************************************** * audio framework engine callbacks */ static int usb_engine_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp) { usb_audio_eng_t *engp = (usb_audio_eng_t *)arg; usb_ac_state_t *statep = engp->statep; int rv = EIO; _NOTE(ARGUNUSED(flag)); if (usb_ac_open(statep->usb_ac_dip) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "usb_ac_open() failed"); return (EIO); } mutex_enter(&engp->lock); engp->intrate = 150; engp->sampsz = engp->fmt.prec / 8; engp->framesz = engp->sampsz * engp->fmt.ch; engp->frsmshift = engp->fmt.ch / 2; engp->smszshift = engp->sampsz / 2; /* * In order to match the requested number of samples per interrupt * from SADA drivers when computing the fragment size, * we need to first truncate the floating point result from * sample rate * channels / intr rate * then adjust up to an even number, before multiplying it * with the sample size */ engp->fragsz = engp->fmt.sr * engp->fmt.ch / engp->intrate; if (engp->fragsz & 1) engp->fragsz++; engp->fragsz *= engp->sampsz; engp->fragfr = engp->fragsz / engp->framesz; engp->nfrags = 10; engp->bufsz = engp->fragsz * engp->nfrags; engp->bufp = kmem_zalloc(engp->bufsz, KM_SLEEP); engp->bufpos = engp->bufp; engp->bufendp = engp->bufp + engp->bufsz; engp->frames = 0; engp->io_count = 0; engp->bufio_count = 0; engp->started = B_FALSE; engp->busy = B_FALSE; *nframesp = engp->nfrags * engp->fragfr; *bufp = engp->bufp; mutex_exit(&engp->lock); if (usb_ac_setup(statep, engp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "device setup failed"); goto OUT; } mutex_enter(&statep->usb_ac_mutex); statep->flags |= AD_SETUP; mutex_exit(&statep->usb_ac_mutex); rv = 0; OUT: if (rv != 0) usb_engine_close(arg); return (rv); } static void usb_engine_close(void *arg) { usb_audio_eng_t *engp = (usb_audio_eng_t *)arg; usb_ac_state_t *statep = engp->statep; mutex_enter(&engp->lock); while (engp->busy) { cv_wait(&engp->usb_audio_cv, &engp->lock); } mutex_exit(&engp->lock); if (statep->flags & AD_SETUP) { usb_ac_teardown(statep, engp); mutex_enter(&statep->usb_ac_mutex); statep->flags &= ~AD_SETUP; mutex_exit(&statep->usb_ac_mutex); } mutex_enter(&engp->lock); if (engp->bufp != NULL) { kmem_free(engp->bufp, engp->bufsz); engp->bufp = NULL; engp->bufpos = NULL; engp->bufendp = NULL; } mutex_exit(&engp->lock); usb_ac_close(statep->usb_ac_dip); } static int usb_engine_start(void *arg) { usb_audio_eng_t *engp = (usb_audio_eng_t *)arg; int rv = 0; int (*start)(usb_ac_state_t *, usb_audio_eng_t *); mutex_enter(&engp->lock); engp->started = B_TRUE; mutex_exit(&engp->lock); usb_ac_state_t *statep = engp->statep; start = ((engp)->af_eflags & ENGINE_OUTPUT_CAP) ? usb_ac_start_play : usb_ac_start_record; if ((*start)(statep, engp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "failed to start %d engine", engp->af_eflags); rv = EIO; } return (rv); } static void usb_engine_stop(void *arg) { usb_audio_eng_t *engp = (usb_audio_eng_t *)arg; mutex_enter(&engp->lock); engp->started = B_FALSE; mutex_exit(&engp->lock); usb_ac_state_t *statep = engp->statep; void (*stop)(usb_ac_state_t *, usb_audio_eng_t *); stop = ((engp)->af_eflags & ENGINE_OUTPUT_CAP) ? usb_ac_stop_play : usb_ac_stop_record; (*stop)(statep, engp); } static uint64_t usb_engine_count(void *arg) { usb_audio_eng_t *engp = arg; uint64_t val; mutex_enter(&engp->lock); val = engp->frames; mutex_exit(&engp->lock); return (val); } static int usb_engine_format(void *arg) { usb_audio_eng_t *engp = arg; switch (engp->fmt.enc) { case USB_AUDIO_FORMAT_TYPE1_MULAW: return (AUDIO_FORMAT_ULAW); case USB_AUDIO_FORMAT_TYPE1_ALAW: return (AUDIO_FORMAT_ALAW); case USB_AUDIO_FORMAT_TYPE1_PCM8: return (AUDIO_FORMAT_U8); case USB_AUDIO_FORMAT_TYPE1_PCM: break; default: return (AUDIO_FORMAT_NONE); } switch (engp->fmt.prec) { case USB_AUDIO_PRECISION_8: return (AUDIO_FORMAT_S8); case USB_AUDIO_PRECISION_16: return (AUDIO_FORMAT_S16_LE); case USB_AUDIO_PRECISION_24: return (AUDIO_FORMAT_S24_LE); case USB_AUDIO_PRECISION_32: return (AUDIO_FORMAT_S32_LE); default: break; } return (AUDIO_FORMAT_NONE); } static int usb_engine_channels(void *arg) { usb_audio_eng_t *engp = arg; return (engp->fmt.ch); } static int usb_engine_rate(void *arg) { usb_audio_eng_t *engp = arg; return (engp->fmt.sr); } /*ARGSUSED*/ static void usb_engine_sync(void *arg, unsigned nframes) { /* Do nothing */ } static unsigned usb_engine_qlen(void *arg) { usb_audio_eng_t *engp = (usb_audio_eng_t *)arg; return (engp->fragfr); } /* * ************************************************************************** * interfaces used by USB audio */ /*ARGSUSED*/ static int usb_change_phy_vol(usb_ac_state_t *statep, int value) { usb_audio_ctrl_t *ctrlp; uint64_t cval = 0; int64_t left, right, delta = 0; ctrlp = statep->controls[CTL_VOLUME_STERO]; ASSERT(value != 0); delta = (value < 0)?-1:1; left = AUDIO_CTRL_STEREO_LEFT(ctrlp->cval) + delta; right = AUDIO_CTRL_STEREO_RIGHT(ctrlp->cval) + delta; if (left > AF_MAX_GAIN) left = AF_MAX_GAIN; if (right > AF_MAX_GAIN) right = AF_MAX_GAIN; if (left < AF_MIN_GAIN) left = AF_MIN_GAIN; if (right < AF_MIN_GAIN) right = AF_MIN_GAIN; cval = AUDIO_CTRL_STEREO_VAL(left, right); if (audio_control_write(ctrlp->af_ctrlp, cval)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, statep->usb_ac_log_handle, "updateing control to value 0x%llx by driver failed", (long long unsigned)cval); return (USB_FAILURE); } return (USB_SUCCESS); }