[prev in list] [next in list] [ prev in thread ] [next in thread] List: openbsd-tech Subject: new USB audio class v2.0 driver From: Alexandre Ratchov <alex () caoua ! org> Date: 2018-12-31 15:58:52 Message-ID: 20181231155852.GF33668 () moule ! localdomain [Download RAW message or body] Hi, Here's a new driver for both USB audio class (UAC) v1.0 and v2.0 devices, it would replace the current one. It focuses on reliability and proper synchronization, including in low-latency configurations. Our current USB sub-system has limitations that currently allow only the following combinations: - USB v2 devices on ehci(4) only - USB v1 devices on uhci(4), ohci(4) or ehci(4) root hub only If you have an audio device that is class compliant (aka vendor claims it's "driverless" on MacOS) *and* one of the above host/hub/device combinations then I'd be very interested in test reports. Especially I'd like to know about possible regressions. To test, apply this diff, rebuild the kernel and set: sndiod_flags=-f rsnd/1 in /etc/rc.conf.local (assuming your uaudio device shows as uaudio1 in dmesg). Then do your regular audio work, let me know how it works and send me output of: dmesg mixerctl -v -f /dev/mixer1 If something is broken, please check if this is a regression. Known bugs and limitations: - Our USB stack requires at least 3 outstanding isochronous transfers to work. So the audio ring buffer size must be at least 3 blocks. If you're using sndiod's -bz options, ensuire the -b argument is at least 3 times the -z argument. - Fractional frames are not used on purpose (except on devices using a feedback pipe for synchronization). This means that only sample rates multiple of 8kHz are reachable. Most azalia(4)'s do so as well. - UAC v2.0 devices with multiple clock sources, clock multipliers and rate converters would use the vendor defaults (by lack of hardware to test). - Certain mixerctl(1) control names would be too long and are truncated. This is hard to fix without changing the mixer(4) interface. - Controls of selectors or mixers are not exposed in the mixer(4) interface, so the device will use vendor defaults there. Such controls are very rare, it seems that MacOS & Windows don't expose them either. Thanks in advance. -- Alexandre Index: uaudio.c =================================================================== RCS file: /cvs/src/sys/dev/usb/uaudio.c,v retrieving revision 1.133 diff -u -p -u -p -r1.133 uaudio.c --- uaudio.c 31 Aug 2018 07:18:18 -0000 1.133 +++ uaudio.c 31 Dec 2018 15:17:23 -0000 @@ -1,3394 +1,4037 @@ -/* $OpenBSD: uaudio.c,v 1.133 2018/08/31 07:18:18 miko Exp $ */ -/* $NetBSD: uaudio.c,v 1.90 2004/10/29 17:12:53 kent Exp $ */ - +/* $OpenBSD$ */ /* - * Copyright (c) 1999 The NetBSD Foundation, Inc. - * All rights reserved. + * Copyright (c) 2018 Alexandre Ratchov <alex@caoua.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. * - * This code is derived from software contributed to The NetBSD Foundation - * by Lennart Augustsson (lennart@augustsson.net) at - * Carlstedt Research & Technology. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - /* - * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf - * http://www.usb.org/developers/devclass_docs/frmts10.pdf - * http://www.usb.org/developers/devclass_docs/termt10.pdf + * The USB Audio Class (UAC) defines what is an audio device and how + * to use it. There are two versions of the UAC: v1.0 and v2.0. They + * are not compatible with each other but they are close enough to + * attempt to have the same driver for both. + * */ - #include <sys/param.h> -#include <sys/systm.h> -#include <sys/kernel.h> -#include <sys/malloc.h> +#include <sys/types.h> #include <sys/device.h> -#include <sys/ioctl.h> -#include <sys/tty.h> +#include <sys/errno.h> #include <sys/fcntl.h> -#include <sys/selinfo.h> -#include <sys/poll.h> - -#include <machine/bus.h> - +#include <sys/malloc.h> +#include <sys/systm.h> +#include <sys/time.h> #include <sys/audioio.h> +#include <machine/bus.h> #include <dev/audio_if.h> - #include <dev/usb/usb.h> -#include <dev/usb/usbdevs.h> #include <dev/usb/usbdi.h> -#include <dev/usb/usbdi_util.h> #include <dev/usb/usbdivar.h> +#include "ehci.h" +#include "xhci.h" -#include <dev/usb/uaudioreg.h> - -/* #define UAUDIO_DEBUG */ #ifdef UAUDIO_DEBUG -#define DPRINTF(x) do { if (uaudiodebug) printf x; } while (0) -#define DPRINTFN(n,x) do { if (uaudiodebug>(n)) printf x; } while (0) -int uaudiodebug = 0; +#define DPRINTF(...) \ + do { \ + if (uaudio_debug) \ + printf(__VA_ARGS__); \ + } while (0) #else -#define DPRINTF(x) -#define DPRINTFN(n,x) +#define DPRINTF(...) do {} while(0) #endif -#define UAUDIO_NCHANBUFS 3 /* number of outstanding request */ -#define UAUDIO_MIN_FRAMES 2 /* ms of sound in each request */ -#define UAUDIO_MAX_FRAMES 16 -#define UAUDIO_NSYNCBUFS 3 /* number of outstanding sync requests */ - -#define UAUDIO_MAX_ALTS 32 /* max alt settings allowed by driver */ - -#define MIX_MAX_CHAN 8 -struct mixerctl { - u_int16_t wValue[MIX_MAX_CHAN]; /* using nchan */ - u_int16_t wIndex; - u_int8_t nchan; - u_int8_t type; -#define MIX_ON_OFF 1 -#define MIX_SIGNED_16 2 -#define MIX_UNSIGNED_16 3 -#define MIX_SIGNED_8 4 -#define MIX_SELECTOR 5 -#define MIX_SIZE(n) ((n) == MIX_SIGNED_16 || (n) == MIX_UNSIGNED_16 ? 2 : 1) -#define MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16) - int minval, maxval; - u_int delta; - u_int8_t class; - char ctlname[MAX_AUDIO_DEV_LEN]; - char *ctlunit; -}; -#define MAKE(h,l) (((h) << 8) | (l)) +#define DEVNAME(sc) ((sc)->dev.dv_xname) -struct as_info { - u_int8_t alt; - u_int8_t encoding; - u_int8_t attributes; /* Copy of bmAttributes of - * usb_audio_streaming_endpoint_descriptor - */ - struct usbd_interface *ifaceh; - const usb_interface_descriptor_t *idesc; - const struct usb_endpoint_descriptor_audio *edesc; - const struct usb_endpoint_descriptor_audio *edesc1; - const struct usb_audio_streaming_type1_descriptor *asf1desc; - int sc_busy; /* currently used */ -}; +/* + * Isochronous endpoint usage (XXX: these belong to sys/usb.h). + */ +#define UE_ISO_USAGE 0x30 +#define UE_ISO_USAGE_DATA 0x00 +#define UE_ISO_USAGE_FEEDBACK 0x10 +#define UE_ISO_USAGE_IMPL 0x20 +#define UE_GET_ISO_USAGE(a) ((a) & UE_ISO_USAGE) + +/* + * Max length of unit names + */ +#define UAUDIO_NAMEMAX MAX_AUDIO_DEV_LEN + +/* + * USB audio class versions + */ +#define UAUDIO_V1 0x100 +#define UAUDIO_V2 0x200 + +/* + * AC class-specific descriptor interface sub-type + */ +#define UAUDIO_AC_HEADER 0x1 +#define UAUDIO_AC_INPUT 0x2 +#define UAUDIO_AC_OUTPUT 0x3 +#define UAUDIO_AC_MIXER 0x4 +#define UAUDIO_AC_SELECTOR 0x5 +#define UAUDIO_AC_FEATURE 0x6 +#define UAUDIO_AC_EFFECT 0x7 +#define UAUDIO_AC_PROCESSING 0x8 +#define UAUDIO_AC_EXTENSION 0x9 +#define UAUDIO_AC_CLKSRC 0xa +#define UAUDIO_AC_CLKSEL 0xb +#define UAUDIO_AC_CLKMULT 0xc +#define UAUDIO_AC_RATECONV 0xd + +/* + * AS class-specific interface sub-types + */ +#define UAUDIO_AS_GENERAL 0x1 +#define UAUDIO_AS_FORMAT 0x2 + +/* + * AS class-specific endpoint sub-type + */ +#define UAUDIO_EP_GENERAL 0x1 + +/* + * UAC v1 formats, wFormatTag is an enum + */ +#define UAUDIO_V1_FMT_PCM 0x1 +#define UAUDIO_V1_FMT_PCM8 0x2 +#define UAUDIO_V1_FMT_FLOAT 0x3 +#define UAUDIO_V1_FMT_ALAW 0x4 +#define UAUDIO_V1_FMT_MULAW 0x5 + +/* + * UAC v2 formats, bmFormats is a bitmap + */ +#define UAUDIO_V2_FMT_PCM 0x01 +#define UAUDIO_V2_FMT_PCM8 0x02 +#define UAUDIO_V2_FMT_FLOAT 0x04 +#define UAUDIO_V2_FMT_ALAW 0x08 +#define UAUDIO_V2_FMT_MULAW 0x10 + +/* + * AC requests + */ +#define UAUDIO_V1_REQ_SET_CUR 0x01 +#define UAUDIO_V1_REQ_SET_MIN 0x02 +#define UAUDIO_V1_REQ_SET_MAX 0x03 +#define UAUDIO_V1_REQ_SET_RES 0x04 +#define UAUDIO_V1_REQ_GET_CUR 0x81 +#define UAUDIO_V1_REQ_GET_MIN 0x82 +#define UAUDIO_V1_REQ_GET_MAX 0x83 +#define UAUDIO_V1_REQ_GET_RES 0x84 +#define UAUDIO_V2_REQ_CUR 1 +#define UAUDIO_V2_REQ_RANGES 2 + +/* + * AC request "selector control" + */ +#define UAUDIO_V2_REQSEL_CLKFREQ 1 +#define UAUDIO_V2_REQSEL_CLKSEL 1 + +/* + * AS class-specific endpoint attributes + */ +#define UAUDIO_EP_FREQCTL 0x01 + +/* + * AC feature control selectors (aka wValue in the request) + */ +#define UAUDIO_REQSEL_MUTE 0x01 +#define UAUDIO_REQSEL_VOLUME 0x02 +#define UAUDIO_REQSEL_BASS 0x03 +#define UAUDIO_REQSEL_MID 0x04 +#define UAUDIO_REQSEL_TREBLE 0x05 +#define UAUDIO_REQSEL_EQ 0x06 +#define UAUDIO_REQSEL_AGC 0x07 +#define UAUDIO_REQSEL_DELAY 0x08 +#define UAUDIO_REQSEL_BASSBOOST 0x09 +#define UAUDIO_REQSEL_LOUDNESS 0x0a +#define UAUDIO_REQSEL_GAIN 0x0b +#define UAUDIO_REQSEL_GAINPAD 0x0c +#define UAUDIO_REQSEL_PHASEINV 0x0d -struct chan { - void (*intr)(void *); /* DMA completion intr handler */ - void *arg; /* arg for intr() */ - struct usbd_pipe *pipe; - struct usbd_pipe *sync_pipe; - - u_int sample_size; - u_int sample_rate; - u_int bytes_per_frame; - u_int max_bytes_per_frame; - u_int fraction; /* fraction/frac_denom is the extra samples/frame */ - u_int frac_denom; /* denominator for fractional samples */ - u_int residue; /* accumulates the fractional samples */ - u_int nframes; /* # of frames per transfer */ - u_int nsync_frames; /* # of frames per sync transfer */ - u_int usb_fps; - u_int maxpktsize; - u_int reqms; /* usb request data duration, in ms */ - u_int hi_speed; - - u_char *start; /* upper layer buffer start */ - u_char *end; /* upper layer buffer end */ - u_char *cur; /* current position in upper layer buffer */ - int blksize; /* chunk size to report up */ - int transferred; /* transferred bytes not reported up */ - - int altidx; /* currently used altidx */ - - int curchanbuf; - int cursyncbuf; - - struct chanbuf { - struct chan *chan; - struct usbd_xfer *xfer; - u_char *buffer; - u_int16_t sizes[UAUDIO_MAX_FRAMES]; - u_int16_t offsets[UAUDIO_MAX_FRAMES]; - u_int16_t size; - } chanbufs[UAUDIO_NCHANBUFS]; - - struct syncbuf { - struct chan *chan; - struct usbd_xfer *xfer; - u_char *buffer; - u_int16_t sizes[UAUDIO_MAX_FRAMES]; - u_int16_t offsets[UAUDIO_MAX_FRAMES]; - u_int16_t size; - } syncbufs[UAUDIO_NSYNCBUFS]; +/* + * Endpoint (UAC v1) or clock-source unit (UAC v2) sample rate control + */ +#define UAUDIO_REQSEL_RATE 0x01 + +/* + * Number of fixed sample rates we support + */ +#define UAUDIO_NRATES (sizeof(uaudio_rates) / sizeof(uaudio_rates[0])) - struct uaudio_softc *sc; /* our softc */ +/* + * read/write pointers for secure sequencial access of binary data, + * ex. usb descriptors, tables and alike. Bytes are read using the + * read pointer up to the write pointer. + */ +struct uaudio_blob { + unsigned char *rptr, *wptr; }; -#define UAUDIO_FLAG_BAD_AUDIO 0x0001 /* claims audio class, but isn't */ -#define UAUDIO_FLAG_NO_FRAC 0x0002 /* don't use fractional samples */ -#define UAUDIO_FLAG_NO_XU 0x0004 /* has broken extension unit */ -#define UAUDIO_FLAG_BAD_ADC 0x0008 /* bad audio spec version number */ -#define UAUDIO_FLAG_VENDOR_CLASS 0x0010 /* claims vendor class but works */ -#define UAUDIO_FLAG_DEPENDENT 0x0020 /* play and record params must equal */ -#define UAUDIO_FLAG_EMU0202 0x0040 -#define UAUDIO_FLAG_BAD_ADC_LEN 0x0080 /* bad audio control descriptor size */ - -struct uaudio_devs { - struct usb_devno uv_dev; - int flags; -} uaudio_devs[] = { - { { USB_VENDOR_YAMAHA, USB_PRODUCT_YAMAHA_UR22 }, - UAUDIO_FLAG_VENDOR_CLASS }, - { { USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ADA70 }, - UAUDIO_FLAG_BAD_ADC } , - { { USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ASC495 }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3G }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3GS }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4_GSM }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4_CDMA }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4S }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_6 }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_TOUCH }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_TOUCH_2G }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_TOUCH_3G }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_TOUCH_4G }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPAD }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPAD2 }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_CREATIVE, USB_PRODUCT_CREATIVE_EMU0202 }, - UAUDIO_FLAG_VENDOR_CLASS | UAUDIO_FLAG_EMU0202 | - UAUDIO_FLAG_DEPENDENT }, - { { USB_VENDOR_DALLAS, USB_PRODUCT_DALLAS_J6502 }, - UAUDIO_FLAG_NO_XU | UAUDIO_FLAG_BAD_ADC }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMNBDLX }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMPRONB }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMPRO4K }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMZOOM }, - UAUDIO_FLAG_BAD_AUDIO }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WEBCAMC200 }, - UAUDIO_FLAG_BAD_ADC_LEN }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WEBCAMC210 }, - UAUDIO_FLAG_BAD_ADC_LEN }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WEBCAMC250 }, - UAUDIO_FLAG_BAD_ADC_LEN }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WEBCAMC270 }, - UAUDIO_FLAG_BAD_ADC_LEN }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WEBCAMC310 }, - UAUDIO_FLAG_BAD_ADC_LEN }, - { { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WEBCAMC500 }, - UAUDIO_FLAG_BAD_ADC_LEN }, - { { USB_VENDOR_TELEX, USB_PRODUCT_TELEX_MIC1 }, - UAUDIO_FLAG_NO_FRAC } +/* + * Ranges of integer values used to represent controls values and + * sample frequencies. + */ +struct uaudio_ranges { + unsigned int nval; + struct uaudio_ranges_el { + struct uaudio_ranges_el *next; + int min, max, res; + } *el; }; -#define uaudio_lookup(v, p) \ - ((struct uaudio_devs *)usb_lookup(uaudio_devs, v, p)) struct uaudio_softc { - struct device sc_dev; /* base device */ - struct usbd_device *sc_udev; /* USB device */ - int sc_ac_iface; /* Audio Control interface */ - struct chan sc_playchan; /* play channel */ - struct chan sc_recchan; /* record channel */ - int sc_nullalt; - int sc_audio_rev; - struct as_info *sc_alts; /* alternate settings */ - int sc_nalts; /* # of alternate settings */ - int sc_altflags; -#define HAS_8 0x01 -#define HAS_16 0x02 -#define HAS_8U 0x04 -#define HAS_ALAW 0x08 -#define HAS_MULAW 0x10 -#define UA_NOFRAC 0x20 /* don't do sample rate adjustment */ -#define HAS_24 0x40 - int sc_mode; /* play/record capability */ - struct mixerctl *sc_ctls; /* mixer controls */ - int sc_nctls; /* # of mixer controls */ - int sc_quirks; -}; + struct device dev; + struct usbd_device *udev; + int version; -struct terminal_list { - int size; - uint16_t terminals[1]; -}; -#define TERMINAL_LIST_SIZE(N) (offsetof(struct terminal_list, terminals) \ - + sizeof(uint16_t) * (N)) + /* + * UAC exposes the device as a circuit of units. Input and + * output jacks are known as terminal units, others are + * processing units. The purpose of this driver is to give + * them reasonable names and expose them as mixer(1) + * controls. Control names are derived from the type of the + * unit and its role in the circuit. + * + * UAC v2.0 exposes also the clock circuitry using units, so + * selecting the sample rate also involves units usage. + */ + struct uaudio_unit { + struct uaudio_unit *unit_next, *src_next, *dst_next; + struct uaudio_unit *src_list, *dst_list; + char name[UAUDIO_NAMEMAX]; + unsigned int nch; + int type, id; + + /* clock source, if a terminal or selector */ + struct uaudio_unit *clock; + + /* sample rates, if this is a clock source */ + struct uaudio_ranges rates; + + /* mixer(4) bits */ +#define UAUDIO_CLASS_REC 0 +#define UAUDIO_CLASS_OUT 1 +#define UAUDIO_CLASS_IN 2 +#define UAUDIO_CLASS_COUNT 3 + int mixer_class; + struct uaudio_mixent { + struct uaudio_mixent *next; + char *fname; +#define UAUDIO_MIX_SW 0 +#define UAUDIO_MIX_NUM 1 +#define UAUDIO_MIX_ENUM 2 + int type; + int chan; + int req_sel; + struct uaudio_ranges ranges; + } *mixent_list; + } *unit_list; + + /* + * Current clock, UAC v2.0 only + */ + struct uaudio_unit *clock; + + /* + * When unique names are needed, they are generated using a + * base string suffixed with a number. Ex. "spkr5". The + * following structure is used to keep track of strings we + * allocated. + */ + struct uaudio_name { + struct uaudio_name *next; + char *templ; + unsigned int unit; + } *names; + + /* + * Audio streaming (AS) alternate settings, i.e. stream format + * and USB-related parameters to use it. + */ + struct uaudio_alt { + struct uaudio_alt *next; + int ifnum, altnum; + int mode; /* one of AUMODE_{RECORD,PLAY} */ + int sync; /* is sync endpoint used */ + int data_addr; /* data endpoint address */ + int sync_addr; /* feedback endpoint address */ + int maxpkt; /* max supported bytes per frame */ + int fps; /* USB (micro-)frames per second */ + int bps, bits, nch; /* audio encoding */ + int v1_rates; /* if UAC 1.0, bitmap of rates */ + } *alts; + + /* + * Audio parameters: play and record stream formats usable + * together. + */ + struct uaudio_params { + struct uaudio_params *next; + struct uaudio_alt *palt, *ralt; + int v1_rates; + } *params_list, *params; + + /* + * One direction audio stream, aka "DMA" in progress + */ + struct uaudio_stream { +#define UAUDIO_NXFERS 3 + struct uaudio_xfer { + struct usbd_xfer *usb_xfer; + unsigned char *buf; + uint16_t *sizes; + unsigned int size; /* bytes requested */ + unsigned int nframes; /* frames requested */ + } data_xfers[UAUDIO_NXFERS], sync_xfers[UAUDIO_NXFERS]; + + unsigned int remain; /* samples 16-bit fixed-point */ + unsigned int spf; /* avg samples per frame */ + unsigned int spf_min, spf_max; /* allowed boundaries */ + + unsigned int maxpkt; + + unsigned int nframes; + unsigned int nframes_min, nframes_max; + + unsigned int data_nextxfer, sync_nextxfer; + struct usbd_pipe *data_pipe; + struct usbd_pipe *sync_pipe; + void (*intr)(void *); + void *arg; + unsigned char *ring_start, *ring_end, *ring_pos; + int ring_offs, ring_blksz, safe_blksz; -struct io_terminal { - union { - const usb_descriptor_t *desc; - const struct usb_audio_input_terminal *it; - const struct usb_audio_output_terminal *ot; - const struct usb_audio_mixer_unit *mu; - const struct usb_audio_selector_unit *su; - const struct usb_audio_feature_unit *fu; - const struct usb_audio_processing_unit *pu; - const struct usb_audio_extension_unit *eu; - } d; - int inputs_size; - struct terminal_list **inputs; /* list of source input terminals */ - struct terminal_list *output; /* list of destination output terminals */ - int direct; /* directly connected to an output terminal */ + /* + * Device clock may take some time to lock during which + * we'd receive null packets for which we need to + * generate silence. We consider that the device clock + * is locked as soon as we receive the first non-null + * packet. + * + * Certain devices (UAC v2 async recording) seem to + * generate rare null microframes, but this seems to be + * caused bogus^sophisticated fractional sample + * calculations, so we shouldn't insert silence in this + * case. + */ + int locked; + } pstream, rstream; + + int ctl_ifnum; /* aka AC interface */ + + int rate; /* current sample rate */ + int mode; /* open() mode */ + int trigger_mode; /* trigger() mode */ + + unsigned int ufps; /* USB frames per second */ + unsigned int sync_pktsz; /* size of sync packet */ + unsigned int nsamp_per_ufr; /* default samples per USB frame */ }; -#define UAC_OUTPUT 0 -#define UAC_INPUT 1 -#define UAC_EQUAL 2 -#define UAC_RECORD 3 -#define UAC_NCLASSES 4 +int uaudio_match(struct device *, void *, void *); +void uaudio_attach(struct device *, struct device *, void *); +int uaudio_detach(struct device *, int); + +int uaudio_open(void *, int); +void uaudio_close(void *); +int uaudio_set_params(void *, int, int, struct audio_params *, + struct audio_params *); +int uaudio_round_blocksize(void *, int); +int uaudio_trigger_output(void *, void *, void *, int, + void (*)(void *), void *, struct audio_params *); +int uaudio_trigger_input(void *, void *, void *, int, + void (*)(void *), void *, struct audio_params *); +int uaudio_halt_output(void *); +int uaudio_halt_input(void *); +int uaudio_query_devinfo(void *, struct mixer_devinfo *); +int uaudio_get_port(void *, struct mixer_ctrl *); +int uaudio_set_port(void *, struct mixer_ctrl *); +int uaudio_get_props(void *); + +int uaudio_process_unit(struct uaudio_softc *, + struct uaudio_unit *, int, + struct uaudio_blob, + struct uaudio_unit **); + +void uaudio_pdata_intr(struct usbd_xfer *, void *, usbd_status); +void uaudio_rdata_intr(struct usbd_xfer *, void *, usbd_status); +void uaudio_psync_intr(struct usbd_xfer *, void *, usbd_status); + #ifdef UAUDIO_DEBUG -const char *uac_names[] = { - AudioCoutputs, AudioCinputs, AudioCequalization, AudioCrecord, -}; +char *uaudio_isoname(int isotype); +char *uaudio_modename(int mode); +char *uaudio_usagename(int usage); +void uaudio_rates_print(int rates); +void uaudio_ranges_print(struct uaudio_ranges *r); +void uaudio_print_unit(struct uaudio_softc *sc, struct uaudio_unit *u); +void uaudio_mixer_print(struct uaudio_softc *sc); +void uaudio_conf_print(struct uaudio_softc *sc); + +/* + * 0 - nothing, as if UAUDIO_DEBUG isn't defined + * 1 - initialisations & setup + * 2 - transfers + */ +int uaudio_debug = 1; #endif -usbd_status uaudio_identify_ac - (struct uaudio_softc *, const usb_config_descriptor_t *); -usbd_status uaudio_identify_as - (struct uaudio_softc *, const usb_config_descriptor_t *); -usbd_status uaudio_process_as - (struct uaudio_softc *, const char *, int *, int, - const usb_interface_descriptor_t *); - -void uaudio_add_alt(struct uaudio_softc *, const struct as_info *); - -const usb_interface_descriptor_t *uaudio_find_iface - (const char *, int, int *, int, int); - -void uaudio_mixer_add_ctl(struct uaudio_softc *, struct mixerctl *); -uByte uaudio_get_cluster_nchan - (int, const struct io_terminal *); -void uaudio_add_input - (struct uaudio_softc *, const struct io_terminal *, int); -void uaudio_add_output - (struct uaudio_softc *, const struct io_terminal *, int); -void uaudio_add_mixer - (struct uaudio_softc *, const struct io_terminal *, int); -void uaudio_add_selector - (struct uaudio_softc *, const struct io_terminal *, int); -#ifdef UAUDIO_DEBUG -const char *uaudio_get_terminal_name(int); -#endif -int uaudio_determine_class - (const struct io_terminal *, struct mixerctl *); -const char *uaudio_feature_name - (const struct io_terminal *, struct mixerctl *); -void uaudio_add_feature - (struct uaudio_softc *, const struct io_terminal *, int); -void uaudio_add_processing_updown - (struct uaudio_softc *, const struct io_terminal *, int); -void uaudio_add_processing - (struct uaudio_softc *, const struct io_terminal *, int); -void uaudio_add_extension - (struct uaudio_softc *, const struct io_terminal *, int); -struct terminal_list *uaudio_merge_terminal_list - (const struct io_terminal *); -struct terminal_list *uaudio_io_terminaltype - (int, struct io_terminal *, int); -usbd_status uaudio_identify - (struct uaudio_softc *, const usb_config_descriptor_t *); - -int uaudio_signext(int, int); -int uaudio_unsignext(int, int); -int uaudio_value2bsd(struct mixerctl *, int); -int uaudio_bsd2value(struct mixerctl *, int); -int uaudio_get(struct uaudio_softc *, int, int, int, int, int); -int uaudio_ctl_get - (struct uaudio_softc *, int, struct mixerctl *, int); -void uaudio_set - (struct uaudio_softc *, int, int, int, int, int, int); -void uaudio_ctl_set - (struct uaudio_softc *, int, struct mixerctl *, int, int); - -usbd_status uaudio_set_speed(struct uaudio_softc *, int, u_int); -void uaudio_set_speed_emu0202(struct chan *ch); - -usbd_status uaudio_chan_open(struct uaudio_softc *, struct chan *); -void uaudio_chan_close(struct uaudio_softc *, struct chan *); -usbd_status uaudio_chan_alloc_buffers - (struct uaudio_softc *, struct chan *); -void uaudio_chan_free_buffers(struct uaudio_softc *, struct chan *); -void uaudio_chan_init - (struct chan *, int, int, const struct audio_params *); -void uaudio_chan_set_param(struct chan *, u_char *, u_char *, int); -void uaudio_chan_ptransfer(struct chan *); -void uaudio_chan_pintr - (struct usbd_xfer *, void *, usbd_status); -void uaudio_chan_psync_transfer(struct chan *); -void uaudio_chan_psync_intr - (struct usbd_xfer *, void *, usbd_status); - -void uaudio_chan_rtransfer(struct chan *); -void uaudio_chan_rintr - (struct usbd_xfer *, void *, usbd_status); - -int uaudio_open(void *, int); -void uaudio_close(void *); -void uaudio_get_minmax_rates - (int, const struct as_info *, const struct audio_params *, - int, int, int, u_long *, u_long *); -int uaudio_match_alt_rate(void *, int, int); -int uaudio_match_alt(void *, struct audio_params *, int); -int uaudio_set_params - (void *, int, int, struct audio_params *, struct audio_params *); -int uaudio_round_blocksize(void *, int); -int uaudio_trigger_output - (void *, void *, void *, int, void (*)(void *), void *, - struct audio_params *); -int uaudio_trigger_input - (void *, void *, void *, int, void (*)(void *), void *, - struct audio_params *); -int uaudio_halt_in_dma(void *); -int uaudio_halt_out_dma(void *); -int uaudio_mixer_set_port(void *, mixer_ctrl_t *); -int uaudio_mixer_get_port(void *, mixer_ctrl_t *); -int uaudio_query_devinfo(void *, mixer_devinfo_t *); -int uaudio_get_props(void *); +extern struct cfdriver ehci_cd; +extern struct cfdriver xhci_cd; -struct audio_hw_if uaudio_hw_if = { - uaudio_open, - uaudio_close, - uaudio_set_params, - uaudio_round_blocksize, - NULL, - NULL, - NULL, - NULL, - NULL, - uaudio_halt_out_dma, - uaudio_halt_in_dma, - NULL, - NULL, - uaudio_mixer_set_port, - uaudio_mixer_get_port, - uaudio_query_devinfo, - NULL, - NULL, - NULL, - uaudio_get_props, - uaudio_trigger_output, - uaudio_trigger_input +struct cfdriver uaudio_cd = { + NULL, "uaudio", DV_DULL }; -int uaudio_match(struct device *, void *, void *); -void uaudio_attach(struct device *, struct device *, void *); -int uaudio_detach(struct device *, int); - -struct cfdriver uaudio_cd = { - NULL, "uaudio", DV_DULL -}; - const struct cfattach uaudio_ca = { sizeof(struct uaudio_softc), uaudio_match, uaudio_attach, uaudio_detach }; -int -uaudio_match(struct device *parent, void *match, void *aux) -{ - struct usb_attach_arg *uaa = aux; - usb_interface_descriptor_t *id; - const usb_interface_descriptor_t *cd_id; - usb_config_descriptor_t *cdesc; - struct uaudio_devs *quirk; - const char *buf; - int flags = 0, size, offs; - - if (uaa->iface == NULL || uaa->device == NULL) - return (UMATCH_NONE); - - quirk = uaudio_lookup(uaa->vendor, uaa->product); - if (quirk) - flags = quirk->flags; - - if (flags & UAUDIO_FLAG_BAD_AUDIO) - return (UMATCH_NONE); - - id = usbd_get_interface_descriptor(uaa->iface); - if (id == NULL) - return (UMATCH_NONE); - - if (!(id->bInterfaceClass == UICLASS_AUDIO || - ((flags & UAUDIO_FLAG_VENDOR_CLASS) && - id->bInterfaceClass == UICLASS_VENDOR))) - return (UMATCH_NONE); +struct audio_hw_if uaudio_hw_if = { + uaudio_open, /* open */ + uaudio_close, /* close */ + uaudio_set_params, /* set_params */ + uaudio_round_blocksize, /* round_blocksize */ + NULL, /* commit_settings */ + NULL, /* init_output */ + NULL, /* init_input */ + NULL, /* start_output */ + NULL, /* start_input */ + uaudio_halt_output, /* halt_output */ + uaudio_halt_input, /* halt_input */ + NULL, /* speaker_ctl */ + NULL, /* setfd */ + uaudio_set_port, /* set_port */ + uaudio_get_port, /* get_port */ + uaudio_query_devinfo, /* query_devinfo */ + NULL, /* malloc, we use bounce buffers :'( */ + NULL, /* free */ + NULL, /* round_buffersize */ + uaudio_get_props, /* get_props */ + uaudio_trigger_output, /* trigger_output */ + uaudio_trigger_input /* trigger_input */ +}; - if (id->bInterfaceSubClass != UISUBCLASS_AUDIOCONTROL) - return (UMATCH_NONE); +/* + * To keep things simple, we support only the following rates, we + * don't care about continuous sample rates or other "advanced" + * features which complicate implementation. + * + * Given that USB2.0 frame rate is 8000 fps, if we require a fixed + * number of samples per frame, only sample rates multiple of 8000Hz + * can be used. To support other rates, we've to use variable number + * of samples per frame such that it achives the desired sample rate + * over one transfer. + */ +int uaudio_rates[] = { + 8000, +#ifdef UAUDIO_USE_FRAC + 11025, +#endif + 12000, + 16000, +#ifdef UAUDIO_USE_FRAC + 22050, +#endif + 24000, + 32000, +#ifdef UAUDIO_USE_FRAC + 44100, +#endif + 48000, + 64000, +#ifdef UAUDIO_USE_FRAC + 88200, +#endif + 96000, + 128000, +#ifdef UAUDIO_USE_FRAC + 176400, +#endif + 192000 +}; - cdesc = usbd_get_config_descriptor(uaa->device); - if (cdesc == NULL) - return (UMATCH_NONE); +/* + * Convert 8, 16, or 24-bit signed value to an int by expanding the + * sign bit. + */ +int +uaudio_sign_expand(unsigned int val, int opsize) +{ + unsigned int s; - size = UGETW(cdesc->wTotalLength); - buf = (const char *)cdesc; + s = 1 << (8 * opsize - 1); + return (val ^ s) - s; +} - offs = 0; - cd_id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOSTREAM, - flags); - if (cd_id == NULL) - return (UMATCH_NONE); - - offs = 0; - cd_id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOCONTROL, - flags); - if (cd_id == NULL) - return (UMATCH_NONE); +/* + * XXX: there are at least 3 such routines in the kernel, but we're + * not supposed to need this. Fix this by using a less stupid audio(9) + * round_blocksize() semantics. + */ +unsigned int +uaudio_gcd(unsigned int a, unsigned int b) +{ + unsigned int r; - return (UMATCH_VENDOR_PRODUCT_CONF_IFACE); + while (b > 0) { + r = a % b; + a = b; + b = r; + } + return a; } -void -uaudio_attach(struct device *parent, struct device *self, void *aux) +int +uaudio_req(struct uaudio_softc *sc, + unsigned int type, + unsigned int req, + unsigned int sel, + unsigned int chan, + unsigned int ifnum, + unsigned int id, + unsigned char *buf, + size_t size) { - struct uaudio_softc *sc = (struct uaudio_softc *)self; - struct usb_attach_arg *uaa = aux; - struct uaudio_devs *quirk; - usb_interface_descriptor_t *id; - usb_config_descriptor_t *cdesc; - usbd_status err; - int i, j, found; - - sc->sc_udev = uaa->device; - - quirk = uaudio_lookup(uaa->vendor, uaa->product); - if (quirk) - sc->sc_quirks = quirk->flags; - - cdesc = usbd_get_config_descriptor(sc->sc_udev); - if (cdesc == NULL) { - printf("%s: failed to get configuration descriptor

", - sc->sc_dev.dv_xname); - return; - } + struct usb_device_request r; + int err; - err = uaudio_identify(sc, cdesc); - if (err) { - printf("%s: audio descriptors make no sense, error=%d

", - sc->sc_dev.dv_xname, err); - return; - } + r.bmRequestType = type; + r.bRequest = req; + USETW(r.wValue, sel << 8 | chan); + USETW(r.wIndex, id << 8 | ifnum); + USETW(r.wLength, size); - /* Pick up the AS interface. */ - for (i = 0; i < uaa->nifaces; i++) { - if (usbd_iface_claimed(sc->sc_udev, i)) - continue; - id = usbd_get_interface_descriptor(uaa->ifaces[i]); - if (id == NULL) - continue; - found = 0; - for (j = 0; j < sc->sc_nalts; j++) { - if (id->bInterfaceNumber == - sc->sc_alts[j].idesc->bInterfaceNumber) { - sc->sc_alts[j].ifaceh = uaa->ifaces[i]; - found = 1; - } - } - if (found) - usbd_claim_iface(sc->sc_udev, i); - } + DPRINTF("req: type = 0x%x, req = 0x%x, val = 0x%x, " + "index = 0x%x, size = %d

", + type, req, UGETW(r.wValue), UGETW(r.wIndex), UGETW(r.wLength)); - for (j = 0; j < sc->sc_nalts; j++) { - if (sc->sc_alts[j].ifaceh == NULL) { - printf("%s: alt %d missing AS interface(s)

", - sc->sc_dev.dv_xname, j); - return; - } + err = usbd_do_request(sc->udev, &r, buf); + if (err) { + printf("%s: request failed: %s

", + DEVNAME(sc), usbd_errstr(err)); + return 0; } + return 1; +} - printf("%s: audio rev %d.%02x", sc->sc_dev.dv_xname, - sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); - - sc->sc_playchan.sc = sc->sc_recchan.sc = sc; - sc->sc_playchan.altidx = -1; - sc->sc_recchan.altidx = -1; +/* + * Read a number of the given size (in bytes) from the given + * blob. Return 0 on error. + */ +int +uaudio_getnum(struct uaudio_blob *p, unsigned int size, unsigned int *ret) +{ + unsigned int i, num = 0; - if (sc->sc_quirks & UAUDIO_FLAG_NO_FRAC) - sc->sc_altflags |= UA_NOFRAC; + if (p->wptr - p->rptr < size) { + DPRINTF("uaudio_getnum: %d: too small

", size); + return 0; + } - printf(", %d mixer controls

", sc->sc_nctls); + for (i = 0; i < size; i++) + num |= *p->rptr++ << (8 * i); - DPRINTF(("%s: doing audio_attach_mi

", __func__)); - audio_attach_mi(&uaudio_hw_if, sc, &sc->sc_dev); + if (ret) + *ret = num; + return 1; } +/* + * Read a USB descriptor from the given blob. Return 0 on error. + */ int -uaudio_detach(struct device *self, int flags) +uaudio_getdesc(struct uaudio_blob *p, struct uaudio_blob *ret) { - struct uaudio_softc *sc = (struct uaudio_softc *)self; + unsigned int size; - /* - * sc_alts may be NULL if uaudio_identify_as() failed, in - * which case uaudio_attach() didn't finish and there's - * nothing to detach. - */ - if (sc->sc_alts == NULL) - return (0); - return (config_detach_children(self, flags)); + if (!uaudio_getnum(p, 1, &size)) + return 0; + if (size-- == 0) { + DPRINTF("uaudio_getdesc: zero sized desc

"); + return 0; + } + if (p->wptr - p->rptr < size) { + DPRINTF("uaudio_getdesc: too small

"); + return 0; + } + ret->rptr = p->rptr; + ret->wptr = p->rptr + size; + p->rptr += size; + return 1; } -const usb_interface_descriptor_t * -uaudio_find_iface(const char *buf, int size, int *offsp, int subtype, int flags) +/* + * Find the unit with the given id, return NULL if not found. + */ +struct uaudio_unit * +uaudio_unit_byid(struct uaudio_softc *sc, unsigned int id) { - const usb_interface_descriptor_t *d; + struct uaudio_unit *u; - while (*offsp < size) { - d = (const void *)(buf + *offsp); - *offsp += d->bLength; - if (d->bDescriptorType == UDESC_INTERFACE && - d->bInterfaceSubClass == subtype && - (d->bInterfaceClass == UICLASS_AUDIO || - (d->bInterfaceClass == UICLASS_VENDOR && - (flags & UAUDIO_FLAG_VENDOR_CLASS)))) - return (d); + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + if (u->id == id) + break; } - return (NULL); + return u; } -void -uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct mixerctl *mc) +/* + * Return a terminal name for the given terminal type. + */ +char * +uaudio_tname(unsigned int type, int isout) { - int res, range; - size_t len; - struct mixerctl *nmc; - - if (mc->class < UAC_NCLASSES) { - DPRINTF(("%s: adding %s.%s

", - __func__, uac_names[mc->class], mc->ctlname)); - } else { - DPRINTF(("%s: adding %s

", __func__, mc->ctlname)); - } + unsigned int hi, lo; + char *name; - nmc = mallocarray(sc->sc_nctls + 1, sizeof(*mc), M_USBDEV, M_NOWAIT); - if (nmc == NULL) { - printf("%s: no memory

", __func__); - return; - } - len = sizeof(*mc) * (sc->sc_nctls + 1); + hi = type >> 8; + lo = type & 0xff; - /* Copy old data, if there was any */ - if (sc->sc_nctls != 0) { - memcpy(nmc, sc->sc_ctls, sizeof(*mc) * (sc->sc_nctls)); - free(sc->sc_ctls, M_USBDEV, sc->sc_nctls * sizeof(*mc)); - } - sc->sc_ctls = nmc; - - mc->delta = 0; - if (mc->type == MIX_ON_OFF) { - mc->minval = 0; - mc->maxval = 1; - } else if (mc->type == MIX_SELECTOR) { - ; - } else { - /* Determine min and max values. */ - mc->minval = uaudio_signext(mc->type, - uaudio_get(sc, GET_MIN, UT_READ_CLASS_INTERFACE, - mc->wValue[0], mc->wIndex, - MIX_SIZE(mc->type))); - mc->maxval = uaudio_signext(mc->type, - uaudio_get(sc, GET_MAX, UT_READ_CLASS_INTERFACE, - mc->wValue[0], mc->wIndex, - MIX_SIZE(mc->type))); - range = mc->maxval - mc->minval; - res = uaudio_get(sc, GET_RES, UT_READ_CLASS_INTERFACE, - mc->wValue[0], mc->wIndex, - MIX_SIZE(mc->type)); - if (res > 0 && range > 0) - mc->delta = (res * 255 + res - 1) / range; - } - - sc->sc_ctls[sc->sc_nctls++] = *mc; - -#ifdef UAUDIO_DEBUG - if (uaudiodebug > 2) { - int i; - DPRINTF(("%s: wValue=%04x", __func__, mc->wValue[0])); - for (i = 1; i < mc->nchan; i++) - DPRINTF((",%04x", mc->wValue[i])); - DPRINTF((" wIndex=%04x type=%d name='%s' unit='%s' " - "min=%d max=%d

", - mc->wIndex, mc->type, mc->ctlname, mc->ctlunit, - mc->minval, mc->maxval)); + switch (hi) { + case 1: + /* usb data stream */ + name = isout ? "record" : "play"; + break; + case 2: + /* embedded inputs */ + name = isout ? "mic-out" : "mic"; + break; + case 3: + /* embedded outputs, mostly speakers, except 0x302 */ + switch (lo) { + case 0x02: + name = isout ? "hp" : "hp-in"; + break; + default: + name = isout ? "spkr" : "spkr-in"; + break; + } + break; + case 4: + /* handsets and headset */ + name = isout ? "spkr" : "mic"; + break; + case 5: + /* phone line */ + name = isout ? "phone-in" : "phone-out"; + break; + case 6: + /* external sources/sinks */ + switch (lo) { + case 0x02: + case 0x05: + case 0x06: + case 0x07: + case 0x09: + case 0x0a: + name = isout ? "dig-out" : "dig-in"; + break; + default: + name = isout ? "line-out" : "line-in"; + break; + } + break; + case 7: + /* internal devices */ + name = isout ? "int-out" : "int-in"; + break; + default: + name = isout ? "unk-out" : "unk-in"; } -#endif + return name; } -uByte -uaudio_get_cluster_nchan(int id, const struct io_terminal *iot) +/* + * Return a clock name for the given clock type. + */ +char * +uaudio_clkname(unsigned int attr) { - struct usb_audio_cluster r; - const usb_descriptor_t *dp; - int i; + static char *names[] = {"ext", "fixed", "var", "prog"}; - for (i = 0; i < 25; i++) { /* avoid infinite loops */ - dp = iot[id].d.desc; - if (dp == 0) - goto bad; - switch (dp->bDescriptorSubtype) { - case UDESCSUB_AC_INPUT: - return (iot[id].d.it->bNrChannels); - case UDESCSUB_AC_OUTPUT: - id = iot[id].d.ot->bSourceId; - break; - case UDESCSUB_AC_MIXER: - r = *(struct usb_audio_cluster *) - &iot[id].d.mu->baSourceId[iot[id].d.mu->bNrInPins]; - return (r.bNrChannels); - case UDESCSUB_AC_SELECTOR: - /* XXX This is not really right */ - id = iot[id].d.su->baSourceId[0]; - break; - case UDESCSUB_AC_FEATURE: - id = iot[id].d.fu->bSourceId; - break; - case UDESCSUB_AC_PROCESSING: - r = *(struct usb_audio_cluster *) - &iot[id].d.pu->baSourceId[iot[id].d.pu->bNrInPins]; - return (r.bNrChannels); - case UDESCSUB_AC_EXTENSION: - r = *(struct usb_audio_cluster *) - &iot[id].d.eu->baSourceId[iot[id].d.eu->bNrInPins]; - return (r.bNrChannels); - default: - goto bad; - } - } -bad: - printf("%s: bad data

", __func__); - return (0); + return names[attr & 3]; } +/* + * Return an unique name for the given template. + */ void -uaudio_add_input(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +uaudio_mkname(struct uaudio_softc *sc, char *templ, char *res) { -#ifdef UAUDIO_DEBUG - const struct usb_audio_input_terminal *d = iot[id].d.it; + struct uaudio_name *n; + char *sep; - DPRINTFN(2,("%s: bTerminalId=%d wTerminalType=0x%04x " - "bAssocTerminal=%d bNrChannels=%d wChannelConfig=%d " - "iChannelNames=%d iTerminal=%d

", - __func__, - d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, - d->bNrChannels, UGETW(d->wChannelConfig), - d->iChannelNames, d->iTerminal)); -#endif + /* + * if this is not a terminal name (i.e. there's a underscore + * in the name, like in "spkr2_mic3"), then use underscore as + * separator to avoid concatenating two numbers + */ + sep = strchr(templ, '_') != NULL ? "_" : ""; + + n = sc->names; + while (1) { + if (n == NULL) { + n = malloc(sizeof(struct uaudio_name), + M_DEVBUF, M_WAITOK); + n->templ = templ; + n->unit = 0; + n->next = sc->names; + sc->names = n; + } + if (strcmp(n->templ, templ) == 0) + break; + n = n->next; + } + if (n->unit == 0) + snprintf(res, UAUDIO_NAMEMAX, "%s", templ); + else + snprintf(res, UAUDIO_NAMEMAX, "%s%s%u", templ, sep, n->unit); + n->unit++; } -void -uaudio_add_output(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +/* + * Convert UAC v1.0 feature bitmap to UAC v2.0 feature bitmap. + */ +unsigned int +uaudio_feature_fixup(struct uaudio_softc *sc, unsigned int ctl) { -#ifdef UAUDIO_DEBUG - const struct usb_audio_output_terminal *d = iot[id].d.ot; + int i; + unsigned int bits, n; - DPRINTFN(2,("%s: bTerminalId=%d wTerminalType=0x%04x " - "bAssocTerminal=%d bSourceId=%d iTerminal=%d

", - __func__, - d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, - d->bSourceId, d->iTerminal)); -#endif + switch (sc->version) { + case UAUDIO_V1: + n = 0; + for (i = 0; i < 16; i++) { + bits = (ctl >> i) & 1; + if (bits) + bits |= 2; + n |= bits << (2 * i); + } + return n; + case UAUDIO_V2: + break; + } + return ctl; } +/* + * Initialize a uaudio_ranges to the empty set + */ void -uaudio_add_mixer(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +uaudio_ranges_init(struct uaudio_ranges *r) { - const struct usb_audio_mixer_unit *d = iot[id].d.mu; - struct usb_audio_mixer_unit_1 *d1; - int c, chs, ochs, i, o, bno, p, mo, mc, k; -#ifdef UAUDIO_DEBUG - int ichs = 0; -#endif - uByte *bm; - struct mixerctl mix; - - DPRINTFN(2,("%s: bUnitId=%d bNrInPins=%d

", __func__, - d->bUnitId, d->bNrInPins)); + r->el = NULL; + r->nval = 0; +} -#ifdef UAUDIO_DEBUG - /* Compute the number of input channels */ - for (i = 0; i < d->bNrInPins; i++) - ichs += uaudio_get_cluster_nchan(d->baSourceId[i], iot); -#endif +/* + * Add the given range to the the uaudio_ranges structures. Ranges are + * not supposed to overlap (required by USB spec). If they do we just + * return. + */ +void +uaudio_ranges_add(struct uaudio_ranges *r, int min, int max, int res) +{ + struct uaudio_ranges_el *e, **pe; - /* and the number of output channels */ - d1 = (struct usb_audio_mixer_unit_1 *)&d->baSourceId[d->bNrInPins]; - ochs = d1->bNrChannels; - DPRINTFN(2,("%s: ichs=%d ochs=%d

", __func__, ichs, ochs)); + if (min > max) { + DPRINTF("uaudio_ranges_add: [%d:%d]/%d: bad range

", + min, max, res); + return; + } - bm = d1->bmControls; - mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); - uaudio_determine_class(&iot[id], &mix); - mix.type = MIX_SIGNED_16; - mix.ctlunit = AudioNvolume; -#define BIT(bno) ((bm[bno / 8] >> (7 - bno % 8)) & 1) - for (p = i = 0; i < d->bNrInPins; i++) { - chs = uaudio_get_cluster_nchan(d->baSourceId[i], iot); - mc = 0; - for (c = 0; c < chs; c++) { - mo = 0; - for (o = 0; o < ochs; o++) { - bno = (p + c) * ochs + o; - if (BIT(bno)) - mo++; - } - if (mo == 1) - mc++; - } - if (mc == chs && chs <= MIX_MAX_CHAN) { - k = 0; - for (c = 0; c < chs; c++) - for (o = 0; o < ochs; o++) { - bno = (p + c) * ochs + o; - if (BIT(bno)) - mix.wValue[k++] = - MAKE(p+c+1, o+1); - } - snprintf(mix.ctlname, sizeof(mix.ctlname), "mix%d-i%d", - d->bUnitId, d->baSourceId[i]); - mix.nchan = chs; - uaudio_mixer_add_ctl(sc, &mix); - } else { - /* XXX */ + for (pe = &r->el; (e = *pe) != NULL; pe = &e->next) { + if (min <= e->max && max >= e->min) { + DPRINTF("uaudio_ranges_add: overlaping ranges

"); + return; } -#undef BIT - p += chs; + if (min < e->max) + break; } + /* XXX: use 'res' here */ + r->nval += max - min + 1; + + e = malloc(sizeof(struct uaudio_ranges_el), M_DEVBUF, M_WAITOK); + e->min = min; + e->max = max; + e->res = res; + e->next = *pe; + *pe = e; } +/* + * Free all ranges making the uaudio_ranges the empty set + */ void -uaudio_add_selector(struct uaudio_softc *sc, const struct io_terminal *iot, int id) -{ - const struct usb_audio_selector_unit *d = iot[id].d.su; - struct mixerctl mix; - int i, wp; - - DPRINTFN(2,("%s: bUnitId=%d bNrInPins=%d

", __func__, - d->bUnitId, d->bNrInPins)); - mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); - mix.wValue[0] = MAKE(0, 0); - uaudio_determine_class(&iot[id], &mix); - mix.nchan = 1; - mix.type = MIX_SELECTOR; - mix.ctlunit = ""; - mix.minval = 1; - mix.maxval = d->bNrInPins; - wp = snprintf(mix.ctlname, MAX_AUDIO_DEV_LEN, "sel%d-", d->bUnitId); - for (i = 1; i <= d->bNrInPins; i++) { - wp += snprintf(mix.ctlname + wp, MAX_AUDIO_DEV_LEN - wp, - "i%d", d->baSourceId[i - 1]); - if (wp > MAX_AUDIO_DEV_LEN - 1) - break; - } - uaudio_mixer_add_ctl(sc, &mix); -} - -#ifdef UAUDIO_DEBUG -const char * -uaudio_get_terminal_name(int terminal_type) -{ - static char buf[100]; - - switch (terminal_type) { - /* USB terminal types */ - case UAT_UNDEFINED: return "UAT_UNDEFINED"; - case UAT_STREAM: return "UAT_STREAM"; - case UAT_VENDOR: return "UAT_VENDOR"; - /* input terminal types */ - case UATI_UNDEFINED: return "UATI_UNDEFINED"; - case UATI_MICROPHONE: return "UATI_MICROPHONE"; - case UATI_DESKMICROPHONE: return "UATI_DESKMICROPHONE"; - case UATI_PERSONALMICROPHONE: return "UATI_PERSONALMICROPHONE"; - case UATI_OMNIMICROPHONE: return "UATI_OMNIMICROPHONE"; - case UATI_MICROPHONEARRAY: return "UATI_MICROPHONEARRAY"; - case UATI_PROCMICROPHONEARR: return "UATI_PROCMICROPHONEARR"; - /* output terminal types */ - case UATO_UNDEFINED: return "UATO_UNDEFINED"; - case UATO_SPEAKER: return "UATO_SPEAKER"; - case UATO_HEADPHONES: return "UATO_HEADPHONES"; - case UATO_DISPLAYAUDIO: return "UATO_DISPLAYAUDIO"; - case UATO_DESKTOPSPEAKER: return "UATO_DESKTOPSPEAKER"; - case UATO_ROOMSPEAKER: return "UATO_ROOMSPEAKER"; - case UATO_COMMSPEAKER: return "UATO_COMMSPEAKER"; - case UATO_SUBWOOFER: return "UATO_SUBWOOFER"; - /* bidir terminal types */ - case UATB_UNDEFINED: return "UATB_UNDEFINED"; - case UATB_HANDSET: return "UATB_HANDSET"; - case UATB_HEADSET: return "UATB_HEADSET"; - case UATB_SPEAKERPHONE: return "UATB_SPEAKERPHONE"; - case UATB_SPEAKERPHONEESUP: return "UATB_SPEAKERPHONEESUP"; - case UATB_SPEAKERPHONEECANC: return "UATB_SPEAKERPHONEECANC"; - /* telephony terminal types */ - case UATT_UNDEFINED: return "UATT_UNDEFINED"; - case UATT_PHONELINE: return "UATT_PHONELINE"; - case UATT_TELEPHONE: return "UATT_TELEPHONE"; - case UATT_DOWNLINEPHONE: return "UATT_DOWNLINEPHONE"; - /* external terminal types */ - case UATE_UNDEFINED: return "UATE_UNDEFINED"; - case UATE_ANALOGCONN: return "UATE_ANALOGCONN"; - case UATE_LINECONN: return "UATE_LINECONN"; - case UATE_LEGACYCONN: return "UATE_LEGACYCONN"; - case UATE_DIGITALAUIFC: return "UATE_DIGITALAUIFC"; - case UATE_SPDIF: return "UATE_SPDIF"; - case UATE_1394DA: return "UATE_1394DA"; - case UATE_1394DV: return "UATE_1394DV"; - /* embedded function terminal types */ - case UATF_UNDEFINED: return "UATF_UNDEFINED"; - case UATF_CALIBNOISE: return "UATF_CALIBNOISE"; - case UATF_EQUNOISE: return "UATF_EQUNOISE"; - case UATF_CDPLAYER: return "UATF_CDPLAYER"; - case UATF_DAT: return "UATF_DAT"; - case UATF_DCC: return "UATF_DCC"; - case UATF_MINIDISK: return "UATF_MINIDISK"; - case UATF_ANALOGTAPE: return "UATF_ANALOGTAPE"; - case UATF_PHONOGRAPH: return "UATF_PHONOGRAPH"; - case UATF_VCRAUDIO: return "UATF_VCRAUDIO"; - case UATF_VIDEODISCAUDIO: return "UATF_VIDEODISCAUDIO"; - case UATF_DVDAUDIO: return "UATF_DVDAUDIO"; - case UATF_TVTUNERAUDIO: return "UATF_TVTUNERAUDIO"; - case UATF_SATELLITE: return "UATF_SATELLITE"; - case UATF_CABLETUNER: return "UATF_CABLETUNER"; - case UATF_DSS: return "UATF_DSS"; - case UATF_RADIORECV: return "UATF_RADIORECV"; - case UATF_RADIOXMIT: return "UATF_RADIOXMIT"; - case UATF_MULTITRACK: return "UATF_MULTITRACK"; - case UATF_SYNTHESIZER: return "UATF_SYNTHESIZER"; - default: - snprintf(buf, sizeof(buf), "unknown type (0x%.4x)", terminal_type); - return buf; +uaudio_ranges_clear(struct uaudio_ranges *r) +{ + struct uaudio_ranges_el *e; + + while ((e = r->el) != NULL) { + r->el = e->next; + free(e, M_DEVBUF, sizeof(struct uaudio_ranges_el)); } + r->nval = 0; } -#endif +/* + * Convert a value in the given uaudio_ranges, into a 0..255 integer + * suitable for mixer usage + */ int -uaudio_determine_class(const struct io_terminal *iot, struct mixerctl *mix) +uaudio_ranges_decode(struct uaudio_ranges *r, int val) { - int terminal_type; + struct uaudio_ranges_el *e; + int diff, pos; - if (iot == NULL || iot->output == NULL) { - mix->class = UAC_OUTPUT; - return 0; - } - terminal_type = 0; - if (iot->output->size == 1) - terminal_type = iot->output->terminals[0]; - /* - * If the only output terminal is USB, - * the class is UAC_RECORD. - */ - if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) { - mix->class = UAC_RECORD; - if (iot->inputs_size == 1 - && iot->inputs[0] != NULL - && iot->inputs[0]->size == 1) - return iot->inputs[0]->terminals[0]; - else - return 0; - } - /* - * If the ultimate destination of the unit is just one output - * terminal and the unit is connected to the output terminal - * directly, the class is UAC_OUTPUT. - */ - if (terminal_type != 0 && iot->direct) { - mix->class = UAC_OUTPUT; - return terminal_type; - } - /* - * If the unit is connected to just one input terminal, - * the class is UAC_INPUT. - */ - if (iot->inputs_size == 1 && iot->inputs[0] != NULL - && iot->inputs[0]->size == 1) { - mix->class = UAC_INPUT; - return iot->inputs[0]->terminals[0]; - } - /* - * Otherwise, the class is UAC_OUTPUT. - */ - mix->class = UAC_OUTPUT; - return terminal_type; -} - -const char * -uaudio_feature_name(const struct io_terminal *iot, struct mixerctl *mix) -{ - int terminal_type; - - terminal_type = uaudio_determine_class(iot, mix); - if (mix->class == UAC_RECORD && terminal_type == 0) - return AudioNmixerout; - DPRINTF(("%s: terminal_type=%s

", __func__, - uaudio_get_terminal_name(terminal_type))); - switch (terminal_type) { - case UAT_STREAM: - return AudioNdac; - - case UATI_MICROPHONE: - case UATI_DESKMICROPHONE: - case UATI_PERSONALMICROPHONE: - case UATI_OMNIMICROPHONE: - case UATI_MICROPHONEARRAY: - case UATI_PROCMICROPHONEARR: - return AudioNmicrophone; - - case UATO_SPEAKER: - case UATO_DESKTOPSPEAKER: - case UATO_ROOMSPEAKER: - case UATO_COMMSPEAKER: - return AudioNspeaker; - - case UATO_HEADPHONES: - return AudioNheadphone; - - case UATO_SUBWOOFER: - return AudioNlfe; - - /* telephony terminal types */ - case UATT_UNDEFINED: - case UATT_PHONELINE: - case UATT_TELEPHONE: - case UATT_DOWNLINEPHONE: - return "phone"; - - case UATE_ANALOGCONN: - case UATE_LINECONN: - case UATE_LEGACYCONN: - return AudioNline; - - case UATE_DIGITALAUIFC: - case UATE_SPDIF: - case UATE_1394DA: - case UATE_1394DV: - return AudioNaux; - - case UATF_CDPLAYER: - return AudioNcd; - - case UATF_SYNTHESIZER: - return AudioNfmsynth; - - case UATF_VIDEODISCAUDIO: - case UATF_DVDAUDIO: - case UATF_TVTUNERAUDIO: - return AudioNvideo; - - case UAT_UNDEFINED: - case UAT_VENDOR: - case UATI_UNDEFINED: -/* output terminal types */ - case UATO_UNDEFINED: - case UATO_DISPLAYAUDIO: -/* bidir terminal types */ - case UATB_UNDEFINED: - case UATB_HANDSET: - case UATB_HEADSET: - case UATB_SPEAKERPHONE: - case UATB_SPEAKERPHONEESUP: - case UATB_SPEAKERPHONEECANC: -/* external terminal types */ - case UATE_UNDEFINED: -/* embedded function terminal types */ - case UATF_UNDEFINED: - case UATF_CALIBNOISE: - case UATF_EQUNOISE: - case UATF_DAT: - case UATF_DCC: - case UATF_MINIDISK: - case UATF_ANALOGTAPE: - case UATF_PHONOGRAPH: - case UATF_VCRAUDIO: - case UATF_SATELLITE: - case UATF_CABLETUNER: - case UATF_DSS: - case UATF_RADIORECV: - case UATF_RADIOXMIT: - case UATF_MULTITRACK: - case 0xffff: - default: - DPRINTF(("%s: 'master' for 0x%.4x

", __func__, terminal_type)); - return AudioNmaster; + pos = 0; + + for (e = r->el; e != NULL; e = e->next) { + if (val >= e->min && val <= e->max) { + pos += val - e->min; + return (r->nval == 1) ? 0 : + (pos * 255 + (r->nval - 1) / 2) / (r->nval - 1); + } + diff = e->max - e->min + 1; + pos += diff; } + return 0; } -void -uaudio_add_feature(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +/* + * Convert a 0..255 to a value in the uaudio_ranges suitable for a USB + * request. + */ +unsigned int +uaudio_ranges_encode(struct uaudio_ranges *r, int val) { - const struct usb_audio_feature_unit *d = iot[id].d.fu; - uByte *ctls = (uByte *)d->bmaControls; - int ctlsize = d->bControlSize; - u_int fumask, mmask, cmask; - struct mixerctl mix; - int chan, ctl, i, nchan, unit; - const char *mixername; + struct uaudio_ranges_el *e; + int diff, pos; -#define GET(i) (ctls[(i)*ctlsize] | \ - (ctlsize > 1 ? ctls[(i)*ctlsize+1] << 8 : 0)) + pos = (val * (r->nval - 1) + 127) / 255; - if (ctlsize == 0) { - DPRINTF(("ignoring feature %d: bControlSize == 0

", id)); - return; + for (e = r->el; e != NULL; e = e->next) { + diff = e->max - e->min + 1; + if (pos < diff) + return e->min + pos; + pos -= diff; } - nchan = (d->bLength - 7) / ctlsize; - mmask = GET(0); - /* Figure out what we can control */ - for (cmask = 0, chan = 1; chan < nchan; chan++) { - DPRINTFN(9,("%s: chan=%d mask=%x

", - __func__, chan, GET(chan))); - cmask |= GET(chan); - } - - DPRINTFN(1,("%s: bUnitId=%d, " - "%d channels, mmask=0x%04x, cmask=0x%04x

", - __func__, d->bUnitId, nchan, mmask, cmask)); - - if (nchan > MIX_MAX_CHAN) - nchan = MIX_MAX_CHAN; - unit = d->bUnitId; - mix.wIndex = MAKE(unit, sc->sc_ac_iface); - for (ctl = MUTE_CONTROL; ctl < LOUDNESS_CONTROL; ctl++) { - fumask = FU_MASK(ctl); - DPRINTFN(4,("%s: ctl=%d fumask=0x%04x

", - __func__, ctl, fumask)); - if (mmask & fumask) { - mix.nchan = 1; - mix.wValue[0] = MAKE(ctl, 0); - } else if (cmask & fumask) { - mix.nchan = nchan - 1; - for (i = 1; i < nchan; i++) { - if (GET(i) & fumask) - mix.wValue[i-1] = MAKE(ctl, i); - else - mix.wValue[i-1] = -1; - } - } else { - continue; - } -#undef GET - mixername = uaudio_feature_name(&iot[id], &mix); - switch (ctl) { - case MUTE_CONTROL: - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNmute); - break; - case VOLUME_CONTROL: - mix.type = MIX_SIGNED_16; - mix.ctlunit = AudioNvolume; - strlcpy(mix.ctlname, mixername, sizeof(mix.ctlname)); - break; - case BASS_CONTROL: - mix.type = MIX_SIGNED_8; - mix.ctlunit = AudioNbass; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNbass); - break; - case MID_CONTROL: - mix.type = MIX_SIGNED_8; - mix.ctlunit = AudioNmid; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNmid); - break; - case TREBLE_CONTROL: - mix.type = MIX_SIGNED_8; - mix.ctlunit = AudioNtreble; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNtreble); - break; - case GRAPHIC_EQUALIZER_CONTROL: - continue; /* XXX don't add anything */ - break; - case AGC_CONTROL: - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), "%s.%s", - mixername, AudioNagc); - break; - case DELAY_CONTROL: - mix.type = MIX_UNSIGNED_16; - mix.ctlunit = "4 ms"; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNdelay); - break; - case BASS_BOOST_CONTROL: - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNbassboost); - break; - case LOUDNESS_CONTROL: - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNloudness); - break; - } - uaudio_mixer_add_ctl(sc, &mix); - } -} - -void -uaudio_add_processing_updown(struct uaudio_softc *sc, - const struct io_terminal *iot, int id) -{ - const struct usb_audio_processing_unit *d = iot[id].d.pu; - const struct usb_audio_processing_unit_1 *d1 = - (const struct usb_audio_processing_unit_1 *)&d->baSourceId[d->bNrInPins]; - const struct usb_audio_processing_unit_updown *ud = - (const struct usb_audio_processing_unit_updown *) - &d1->bmControls[d1->bControlSize]; - struct mixerctl mix; - int i; - - DPRINTFN(2,("%s: bUnitId=%d bNrModes=%d

", - __func__, d->bUnitId, ud->bNrModes)); + return 0; +} - if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { - DPRINTF(("%s: no mode select

", __func__)); - return; +/* + * Return the bitmap of supported rates included in the given ranges. + * This is not a mixer thing, UAC v2.0 uses ranges to report sample + * rates. + */ +int +uaudio_ranges_getrates(struct uaudio_ranges *r, + unsigned int mult, unsigned int div) +{ + struct uaudio_ranges_el *e; + int rates, i, v; + + rates = 0; + + for (e = r->el; e != NULL; e = e->next) { + for (i = 0; i < UAUDIO_NRATES; i++) { + v = (unsigned long long)uaudio_rates[i] * mult / div; + if (v < e->min || v > e->max) + continue; + if (e->res == 0 || v - e->min % e->res == 0) + rates |= 1 << i; + } } - mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); - mix.nchan = 1; - mix.wValue[0] = MAKE(UD_MODE_SELECT_CONTROL, 0); - uaudio_determine_class(&iot[id], &mix); - mix.type = MIX_ON_OFF; /* XXX */ - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), "pro%d-mode", d->bUnitId); - - for (i = 0; i < ud->bNrModes; i++) { - DPRINTFN(2,("%s: i=%d bm=0x%x

", - __func__, i, UGETW(ud->waModes[i]))); - /* XXX */ - } - uaudio_mixer_add_ctl(sc, &mix); + return rates; } -void -uaudio_add_processing(struct uaudio_softc *sc, const struct io_terminal *iot, int \ id) -{ - const struct usb_audio_processing_unit *d = iot[id].d.pu; - const struct usb_audio_processing_unit_1 *d1 = - (const struct usb_audio_processing_unit_1 *)&d->baSourceId[d->bNrInPins]; - int ptype = UGETW(d->wProcessType); - struct mixerctl mix; - - DPRINTFN(2,("%s: wProcessType=%d bUnitId=%d " - "bNrInPins=%d

", __func__, ptype, d->bUnitId, - d->bNrInPins)); - - if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) { - mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); - mix.nchan = 1; - mix.wValue[0] = MAKE(XX_ENABLE_CONTROL, 0); - uaudio_determine_class(&iot[id], &mix); - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), "pro%d.%d-enable", - d->bUnitId, ptype); - uaudio_mixer_add_ctl(sc, &mix); - } - - switch(ptype) { - case UPDOWNMIX_PROCESS: - uaudio_add_processing_updown(sc, iot, id); - break; - case DOLBY_PROLOGIC_PROCESS: - case P3D_STEREO_EXTENDER_PROCESS: - case REVERBATION_PROCESS: - case CHORUS_PROCESS: - case DYN_RANGE_COMP_PROCESS: - default: - DPRINTF(("%s: unit %d, type=%d not impl.

", - __func__, d->bUnitId, ptype)); - break; +/* + * Return the index in the uaudio_rates[] array of rate closest to the + * given rate in Hz. + */ +int +uaudio_rates_indexof(int mask, int rate) +{ + int i, diff, best_index, best_diff; + + best_index = -1; + best_diff = INT_MAX; + for (i = 0; i < UAUDIO_NRATES; i++) { + if ((mask & (1 << i)) == 0) + continue; + diff = uaudio_rates[i] - rate; + if (diff < 0) + diff = -diff; + if (diff < best_diff) { + best_index = i; + best_diff = diff; + } } + return best_index; } -void -uaudio_add_extension(struct uaudio_softc *sc, const struct io_terminal *iot, int id) -{ - const struct usb_audio_extension_unit *d = iot[id].d.eu; - const struct usb_audio_extension_unit_1 *d1 = - (const struct usb_audio_extension_unit_1 *)&d->baSourceId[d->bNrInPins]; - struct mixerctl mix; +/* + * Do a request that results in a uaudio_ranges. On UAC v1.0, this is + * simply a min/max/res triplet. On UAC v2.0, this is an array of + * min/max/res triplets. + */ +int +uaudio_req_ranges(struct uaudio_softc *sc, + unsigned int opsize, + unsigned int sel, + unsigned int chan, + unsigned int ifnum, + unsigned int id, + struct uaudio_ranges *r) +{ + unsigned char req_buf[16], *req = NULL; + size_t req_size; + struct uaudio_blob p; + unsigned int count, min, max, res; + int i; - DPRINTFN(2,("%s: bUnitId=%d bNrInPins=%d

", - __func__, d->bUnitId, d->bNrInPins)); + switch (sc->version) { + case UAUDIO_V1: + count = 1; + req = req_buf; + p.rptr = p.wptr = req; + if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE, + UAUDIO_V1_REQ_GET_MIN, sel, chan, + ifnum, id, p.wptr, opsize)) + return 0; + p.wptr += opsize; + if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE, + UAUDIO_V1_REQ_GET_MAX, sel, chan, + ifnum, id, p.wptr, opsize)) + return 0; + p.wptr += opsize; + if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE, + UAUDIO_V1_REQ_GET_RES, sel, chan, + ifnum, id, p.wptr, opsize)) + return 0; + p.wptr += opsize; + break; + case UAUDIO_V2: + /* fetch the ranges count only (first 2 bytes) */ + if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE, + UAUDIO_V2_REQ_RANGES, sel, chan, + ifnum, id, req_buf, 2)) + return 0; + count = req_buf[0] | req_buf[1] << 8; - if (sc->sc_quirks & UAUDIO_FLAG_NO_XU) - return; + /* restart the request on a large enough buffer */ + req_size = 2 + 3 * opsize * count; + if (sizeof(req_buf) >= req_size) + req = req_buf; + else + req = malloc(req_size, M_DEVBUF, M_WAITOK); - if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) { - mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); - mix.nchan = 1; - mix.wValue[0] = MAKE(UA_EXT_ENABLE, 0); - uaudio_determine_class(&iot[id], &mix); - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), "ext%d-enable", - d->bUnitId); - uaudio_mixer_add_ctl(sc, &mix); + p.rptr = p.wptr = req; + if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE, + UAUDIO_V2_REQ_RANGES, sel, chan, + ifnum, id, p.wptr, req_size)) + return 0; + p.wptr += req_size; + + /* skip initial 2 bytes of count */ + p.rptr += 2; + break; + } + + for (i = 0; i < count; i++) { + if (!uaudio_getnum(&p, opsize, &min)) + return 0; + if (!uaudio_getnum(&p, opsize, &max)) + return 0; + if (!uaudio_getnum(&p, opsize, &res)) + return 0; + uaudio_ranges_add(r, + uaudio_sign_expand(min, opsize), + uaudio_sign_expand(max, opsize), + uaudio_sign_expand(res, opsize)); } + + if (req != req_buf) + free(req, M_DEVBUF, req_size); + + return 1; } -struct terminal_list* -uaudio_merge_terminal_list(const struct io_terminal *iot) -{ - struct terminal_list *tml; - uint16_t *ptm; - int i, len; - - len = 0; - if (iot->inputs == NULL) - return NULL; - for (i = 0; i < iot->inputs_size; i++) { - if (iot->inputs[i] != NULL) - len += iot->inputs[i]->size; - } - tml = malloc(TERMINAL_LIST_SIZE(len), M_TEMP, M_NOWAIT); - if (tml == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - tml->size = 0; - ptm = tml->terminals; - for (i = 0; i < iot->inputs_size; i++) { - if (iot->inputs[i] == NULL) - continue; - if (iot->inputs[i]->size > len) - break; - memcpy(ptm, iot->inputs[i]->terminals, - iot->inputs[i]->size * sizeof(uint16_t)); - tml->size += iot->inputs[i]->size; - ptm += iot->inputs[i]->size; - len -= iot->inputs[i]->size; - } - return tml; -} - -struct terminal_list * -uaudio_io_terminaltype(int outtype, struct io_terminal *iot, int id) -{ - struct terminal_list *tml; - struct io_terminal *it; - int src_id, i; - - it = &iot[id]; - if (it->output != NULL) { - /* already has outtype? */ - for (i = 0; i < it->output->size; i++) - if (it->output->terminals[i] == outtype) - return uaudio_merge_terminal_list(it); - tml = malloc(TERMINAL_LIST_SIZE(it->output->size + 1), - M_TEMP, M_NOWAIT); - if (tml == NULL) { - printf("%s: no memory

", __func__); - return uaudio_merge_terminal_list(it); - } - memcpy(tml, it->output, TERMINAL_LIST_SIZE(it->output->size)); - tml->terminals[it->output->size] = outtype; - tml->size++; - free(it->output, M_TEMP, 0); - it->output = tml; - if (it->inputs != NULL) { - for (i = 0; i < it->inputs_size; i++) - free(it->inputs[i], M_TEMP, 0); - free(it->inputs, M_TEMP, 0); - } - it->inputs_size = 0; - it->inputs = NULL; - } else { /* end `iot[id] != NULL' */ - it->inputs_size = 0; - it->inputs = NULL; - it->output = malloc(TERMINAL_LIST_SIZE(1), M_TEMP, M_NOWAIT); - if (it->output == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - it->output->terminals[0] = outtype; - it->output->size = 1; - it->direct = 0; - } - - switch (it->d.desc->bDescriptorSubtype) { - case UDESCSUB_AC_INPUT: - it->inputs = malloc(sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); - if (it->inputs == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - tml = malloc(TERMINAL_LIST_SIZE(1), M_TEMP, M_NOWAIT); - if (tml == NULL) { - printf("%s: no memory

", __func__); - free(it->inputs, M_TEMP, 0); - it->inputs = NULL; - return NULL; - } - it->inputs[0] = tml; - tml->terminals[0] = UGETW(it->d.it->wTerminalType); - tml->size = 1; - it->inputs_size = 1; - return uaudio_merge_terminal_list(it); - case UDESCSUB_AC_FEATURE: - src_id = it->d.fu->bSourceId; - it->inputs = malloc(sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); - if (it->inputs == NULL) { - printf("%s: no memory

", __func__); - return uaudio_io_terminaltype(outtype, iot, src_id); - } - it->inputs[0] = uaudio_io_terminaltype(outtype, iot, src_id); - it->inputs_size = 1; - return uaudio_merge_terminal_list(it); - case UDESCSUB_AC_OUTPUT: - it->inputs = malloc(sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); - if (it->inputs == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - src_id = it->d.ot->bSourceId; - it->inputs[0] = uaudio_io_terminaltype(outtype, iot, src_id); - it->inputs_size = 1; - iot[src_id].direct = 1; - return NULL; - case UDESCSUB_AC_MIXER: - it->inputs_size = 0; - it->inputs = mallocarray(it->d.mu->bNrInPins, - sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); - if (it->inputs == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - for (i = 0; i < it->d.mu->bNrInPins; i++) { - src_id = it->d.mu->baSourceId[i]; - it->inputs[i] = uaudio_io_terminaltype(outtype, iot, - src_id); - it->inputs_size++; - } - return uaudio_merge_terminal_list(it); - case UDESCSUB_AC_SELECTOR: - it->inputs_size = 0; - it->inputs = mallocarray(it->d.su->bNrInPins, - sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); - if (it->inputs == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - for (i = 0; i < it->d.su->bNrInPins; i++) { - src_id = it->d.su->baSourceId[i]; - it->inputs[i] = uaudio_io_terminaltype(outtype, iot, - src_id); - it->inputs_size++; - } - return uaudio_merge_terminal_list(it); - case UDESCSUB_AC_PROCESSING: - it->inputs_size = 0; - it->inputs = mallocarray(it->d.pu->bNrInPins, - sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); - if (it->inputs == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - for (i = 0; i < it->d.pu->bNrInPins; i++) { - src_id = it->d.pu->baSourceId[i]; - it->inputs[i] = uaudio_io_terminaltype(outtype, iot, - src_id); - it->inputs_size++; - } - return uaudio_merge_terminal_list(it); - case UDESCSUB_AC_EXTENSION: - it->inputs_size = 0; - it->inputs = mallocarray(it->d.eu->bNrInPins, - sizeof(struct terminal_list *), M_TEMP, M_NOWAIT); - if (it->inputs == NULL) { - printf("%s: no memory

", __func__); - return NULL; - } - for (i = 0; i < it->d.eu->bNrInPins; i++) { - src_id = it->d.eu->baSourceId[i]; - it->inputs[i] = uaudio_io_terminaltype(outtype, iot, - src_id); - it->inputs_size++; +/* + * Return the rates bitmap of the given interface alt setting + */ +int +uaudio_alt_getrates(struct uaudio_softc *sc, struct uaudio_alt *p) +{ + struct uaudio_unit *u; + unsigned int mult = 1, div = 1; + + switch (sc->version) { + case UAUDIO_V1: + return p->v1_rates; + case UAUDIO_V2: + u = sc->clock; + while (1) { + switch (u->type) { + case UAUDIO_AC_CLKSRC: + return uaudio_ranges_getrates(&u->rates, + mult, div); + case UAUDIO_AC_CLKSEL: + u = u->clock; + break; + case UAUDIO_AC_CLKMULT: + case UAUDIO_AC_RATECONV: + /* XXX: adjust rate with multiplier */ + u = u->src_list; + break; + default: + DPRINTF("uaudio_alt_getrates: no clock

"); + return 0; + } } - return uaudio_merge_terminal_list(it); - case UDESCSUB_AC_HEADER: - default: - return NULL; } + return 0; } -usbd_status -uaudio_identify(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) +/* + * Return the rates bitmap of the given parameters setting + */ +int +uaudio_getrates(struct uaudio_softc *sc, struct uaudio_params *p) { - usbd_status err; - - err = uaudio_identify_ac(sc, cdesc); - if (err) - return (err); - return (uaudio_identify_as(sc, cdesc)); + return uaudio_alt_getrates(sc, p->palt ? p->palt : p->ralt); } +/* + * Add the given feature (aka mixer control) to the given unit. + */ void -uaudio_add_alt(struct uaudio_softc *sc, const struct as_info *ai) +uaudio_feature_addent(struct uaudio_softc *sc, + struct uaudio_unit *u, int uac_type, int chan) { - struct as_info *nai; + static struct { + char *name; + int mix_type; + int req_sel; + } features[] = { + {"mute", UAUDIO_MIX_SW, UAUDIO_REQSEL_MUTE}, + {"level", UAUDIO_MIX_NUM, UAUDIO_REQSEL_VOLUME}, + {"bass", UAUDIO_MIX_NUM, UAUDIO_REQSEL_BASS}, + {"mid", UAUDIO_MIX_NUM, UAUDIO_REQSEL_MID}, + {"treble", UAUDIO_MIX_NUM, UAUDIO_REQSEL_TREBLE}, + {"eq", UAUDIO_MIX_NUM, UAUDIO_REQSEL_EQ}, + {"agc", UAUDIO_MIX_SW, UAUDIO_REQSEL_AGC}, + {NULL, -1, -1}, /* delay */ + {"bassboost", UAUDIO_MIX_SW, UAUDIO_REQSEL_BASSBOOST}, + {"loud", UAUDIO_MIX_SW, UAUDIO_REQSEL_LOUDNESS}, + {"gain", UAUDIO_MIX_NUM, UAUDIO_REQSEL_GAIN}, + {"gainpad", UAUDIO_MIX_SW, UAUDIO_REQSEL_GAINPAD}, + {"phase", UAUDIO_MIX_SW, UAUDIO_REQSEL_PHASEINV}, + {NULL, -1, -1}, /* undeflow */ + {NULL, -1, -1} /* overflow */ + }; + struct uaudio_mixent *m, *i, **pi; + int cmp; - nai = mallocarray(sc->sc_nalts + 1, sizeof(*ai), M_USBDEV, M_NOWAIT); - if (nai == NULL) { - printf("%s: no memory

", __func__); + if (uac_type >= sizeof(features) / sizeof(features[0])) { + printf("%s: skipped unknown feature

", DEVNAME(sc)); return; } - /* Copy old data, if there was any */ - if (sc->sc_nalts != 0) { - memcpy(nai, sc->sc_alts, sizeof(*ai) * (sc->sc_nalts)); - free(sc->sc_alts, M_USBDEV, sc->sc_nalts * sizeof(*ai)); - } - sc->sc_alts = nai; - DPRINTFN(2,("%s: adding alt=%d, enc=%d

", - __func__, ai->alt, ai->encoding)); - sc->sc_alts[sc->sc_nalts++] = *ai; -} - -usbd_status -uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, - int size, const usb_interface_descriptor_t *id) -#define offs (*offsp) -{ - const struct usb_audio_streaming_interface_descriptor *asid; - const struct usb_audio_streaming_type1_descriptor *asf1d; - const struct usb_endpoint_descriptor_audio *ed; - const struct usb_endpoint_descriptor_audio *sync_ed; - const struct usb_audio_streaming_endpoint_descriptor *sed; - int format, chan, prec, enc, bps; - int dir, type, sync, sync_addr; - struct as_info ai; - const char *format_str; - - asid = (const void *)(buf + offs); - if (asid->bDescriptorType != UDESC_CS_INTERFACE || - asid->bDescriptorSubtype != AS_GENERAL) - return (USBD_INVAL); - DPRINTF(("%s: asid: bTerminalLink=%d wFormatTag=%d

", __func__, - asid->bTerminalLink, UGETW(asid->wFormatTag))); - offs += asid->bLength; - if (offs > size) - return (USBD_INVAL); - - asf1d = (const void *)(buf + offs); - if (asf1d->bDescriptorType != UDESC_CS_INTERFACE || - asf1d->bDescriptorSubtype != FORMAT_TYPE) - return (USBD_INVAL); - offs += asf1d->bLength; - if (offs > size) - return (USBD_INVAL); - - if (asf1d->bFormatType != FORMAT_TYPE_I) { - printf("%s: ignored setting with type %d format

", - sc->sc_dev.dv_xname, UGETW(asid->wFormatTag)); - return (USBD_NORMAL_COMPLETION); - } - - ed = (const void *)(buf + offs); - if (ed->bDescriptorType != UDESC_ENDPOINT) - return (USBD_INVAL); - DPRINTF(("%s: endpoint[0] bLength=%d bDescriptorType=%d " - "bEndpointAddress=%d bmAttributes=0x%x wMaxPacketSize=%d " - "bInterval=%d bRefresh=%d bSynchAddress=%d

", - __func__, - ed->bLength, ed->bDescriptorType, ed->bEndpointAddress, - ed->bmAttributes, UGETW(ed->wMaxPacketSize), - ed->bInterval, ed->bRefresh, ed->bSynchAddress)); - offs += ed->bLength; - if (offs > size) - return (USBD_INVAL); - if (UE_GET_XFERTYPE(ed->bmAttributes) != UE_ISOCHRONOUS) - return (USBD_INVAL); - - dir = UE_GET_DIR(ed->bEndpointAddress); - type = UE_GET_ISO_TYPE(ed->bmAttributes); - - /* Check for sync endpoint. */ - sync = 0; - sync_addr = 0; - if (id->bNumEndpoints > 1 && - ((dir == UE_DIR_IN && type == UE_ISO_ADAPT) || - (dir != UE_DIR_IN && type == UE_ISO_ASYNC))) - sync = 1; - - /* Check whether sync endpoint address is given. */ - if (ed->bLength >= USB_ENDPOINT_DESCRIPTOR_AUDIO_SIZE) { - /* bSynchAdress set to 0 indicates sync is not used. */ - if (ed->bSynchAddress == 0) - sync = 0; - else - sync_addr = ed->bSynchAddress; + m = malloc(sizeof(struct uaudio_mixent), M_DEVBUF, M_WAITOK); + m->chan = chan; + m->fname = features[uac_type].name; + m->type = features[uac_type].mix_type; + m->req_sel = features[uac_type].req_sel; + uaudio_ranges_init(&m->ranges); + + if (m->type == UAUDIO_MIX_NUM) { + if (!uaudio_req_ranges(sc, 2, + m->req_sel, chan < 0 ? 0 : chan + 1, + sc->ctl_ifnum, u->id, + &m->ranges)) { + DPRINTF("%s.%s[%d]: failed

", u->name, m->fname, chan); + free(m, M_DEVBUF, sizeof(struct uaudio_mixent)); + return; + } + if (m->ranges.el == NULL) { + printf("%s: skipped %s control with empty range

", + DEVNAME(sc), m->fname); + free(m, M_DEVBUF, sizeof(struct uaudio_mixent)); + return; + } +#ifdef UAUDIO_DEBUG + if (uaudio_debug) + uaudio_ranges_print(&m->ranges); +#endif + } + + /* + * Add to unit's mixer controls list, sorting entries by name + * and increasing channel number. + */ + for (pi = &u->mixent_list; (i = *pi) != NULL; pi = &i->next) { + cmp = strcmp(i->fname, m->fname); + if (cmp == 0) + cmp = i->chan - m->chan; + if (cmp == 0) { + DPRINTF("%02u: %s.%s: duplicate feature for chan %d

", + u->id, u->name, m->fname, m->chan); + free(m, M_DEVBUF, sizeof(struct uaudio_mixent)); + return; + } + if (cmp > 0) + break; + } + m->next = *pi; + *pi = m; + + DPRINTF("\t%s[%d]

", m->fname, m->chan); +} + +/* + * For the given unit, parse the list of its sources and recursively + * call uaudio_process_unit() for each. + */ +int +uaudio_process_srcs(struct uaudio_softc *sc, + struct uaudio_unit *u, struct uaudio_blob units, + struct uaudio_blob *p) +{ + struct uaudio_unit *s, **ps; + unsigned int i, npin, sid; + + if (!uaudio_getnum(p, 1, &npin)) + return 0; + ps = &u->src_list; + for (i = 0; i < npin; i++) { + if (!uaudio_getnum(p, 1, &sid)) + return 0; + if (!uaudio_process_unit(sc, u, sid, units, &s)) + return 0; + s->src_next = NULL; + *ps = s; + ps = &s->src_next; } + return 1; +} - sed = (const void *)(buf + offs); - if (sed->bDescriptorType != UDESC_CS_ENDPOINT || - sed->bDescriptorSubtype != AS_GENERAL) - return (USBD_INVAL); - DPRINTF((" streaming_endpoint: offset=%d bLength=%d

", offs, sed->bLength)); - offs += sed->bLength; - if (offs > size) - return (USBD_INVAL); - - sync_ed = NULL; - if (sync == 1) { - sync_ed = (const void*)(buf + offs); - if (sync_ed->bDescriptorType != UDESC_ENDPOINT) { - printf("%s: sync ep descriptor wrong type

", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - DPRINTF(("%s: endpoint[1] bLength=%d " - "bDescriptorType=%d bEndpointAddress=%d " - "bmAttributes=0x%x wMaxPacketSize=%d bInterval=%d " - "bRefresh=%d bSynchAddress=%d

", - __func__, - sync_ed->bLength, sync_ed->bDescriptorType, - sync_ed->bEndpointAddress, sync_ed->bmAttributes, - UGETW(sync_ed->wMaxPacketSize), sync_ed->bInterval, - sync_ed->bRefresh, sync_ed->bSynchAddress)); - offs += sync_ed->bLength; - if (offs > size) { - printf("%s: sync ep descriptor too large

", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - if (dir == UE_GET_DIR(sync_ed->bEndpointAddress)) { - printf("%s: sync ep wrong direction

", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - if (UE_GET_XFERTYPE(sync_ed->bmAttributes) != UE_ISOCHRONOUS) { - printf("%s: sync ep wrong xfer type

", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - if (sync_ed->bLength >= - USB_ENDPOINT_DESCRIPTOR_AUDIO_SIZE && - sync_ed->bSynchAddress != 0) { - printf("%s: sync ep bSynchAddress != 0

", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - if (sync_addr && - UE_GET_ADDR(sync_ed->bEndpointAddress) != - UE_GET_ADDR(sync_addr)) { - printf("%s: sync ep address mismatch

", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - } - if (sync_ed != NULL && dir == UE_DIR_IN) { - printf("%s: sync pipe for recording not yet implemented

", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - - format = UGETW(asid->wFormatTag); - chan = asf1d->bNrChannels; - prec = asf1d->bBitResolution; - bps = asf1d->bSubFrameSize; - if ((prec != 8 && prec != 16 && prec != 24) || (bps < 1 || bps > 4)) { - printf("%s: ignored setting with precision %d bps %d

", - sc->sc_dev.dv_xname, prec, bps); - return (USBD_NORMAL_COMPLETION); - } - switch (format) { - case UA_FMT_PCM: - if (prec == 8) { - sc->sc_altflags |= HAS_8; - } else if (prec == 16) { - sc->sc_altflags |= HAS_16; - } else if (prec == 24) { - sc->sc_altflags |= HAS_24; - } - enc = AUDIO_ENCODING_SLINEAR_LE; - format_str = "pcm"; - break; - case UA_FMT_PCM8: - enc = AUDIO_ENCODING_ULINEAR_LE; - sc->sc_altflags |= HAS_8U; - format_str = "pcm8"; - break; - case UA_FMT_ALAW: - enc = AUDIO_ENCODING_ALAW; - sc->sc_altflags |= HAS_ALAW; - format_str = "alaw"; - break; - case UA_FMT_MULAW: - enc = AUDIO_ENCODING_ULAW; - sc->sc_altflags |= HAS_MULAW; - format_str = "mulaw"; +/* + * Parse the number of channels. + */ +int +uaudio_process_nch(struct uaudio_softc *sc, + struct uaudio_unit *u, struct uaudio_blob *p) +{ + if (!uaudio_getnum(p, 1, &u->nch)) + return 0; + /* skip junk */ + switch (sc->version) { + case UAUDIO_V1: + if (!uaudio_getnum(p, 2, NULL)) /* bmChannelConfig */ + return 0; + break; + case UAUDIO_V2: + if (!uaudio_getnum(p, 4, NULL)) /* wChannelConfig */ + return 0; break; - case UA_FMT_IEEE_FLOAT: - default: - printf("%s: ignored setting with format %d

", - sc->sc_dev.dv_xname, format); - return (USBD_NORMAL_COMPLETION); } + if (!uaudio_getnum(p, 1, NULL)) /* iChannelNames */ + return 0; + return 1; +} + +/* + * Find the AC class-specific descriptor for this unit id. + */ +int +uaudio_unit_getdesc(struct uaudio_softc *sc, int id, + struct uaudio_blob units, + struct uaudio_blob *p, + unsigned int *rtype) +{ + unsigned int i, type, subtype; + + /* + * Find the usb descriptor for this id. + */ + while (1) { + if (units.rptr == units.wptr) { + DPRINTF("uaudio_unit_getdesc: %02u: not found

", id); + return 0; + } + if (!uaudio_getdesc(&units, p)) + return 0; + if (!uaudio_getnum(p, 1, &type)) + return 0; + if (!uaudio_getnum(p, 1, &subtype)) + return 0; + if (!uaudio_getnum(p, 1, &i)) + return 0; + if (i == id) + break; + } + *rtype = subtype; + return 1; +} + +/* + * Parse a unit, possibly calling uaudio_process_unit() for each of + * its sources. + */ +int +uaudio_process_unit(struct uaudio_softc *sc, + struct uaudio_unit *dest, int id, + struct uaudio_blob units, + struct uaudio_unit **rchild) +{ + struct uaudio_blob p; + struct uaudio_unit *u, *s; + unsigned int i, j, term, size, attr, ctl, type, subtype, assoc, clk; #ifdef UAUDIO_DEBUG - printf("%s: %s: %d-ch %d-bit %d-byte %s,", sc->sc_dev.dv_xname, - dir == UE_DIR_IN ? "recording" : "playback", - chan, prec, bps, format_str); - if (asf1d->bSamFreqType == UA_SAMP_CONTNUOUS) { - printf(" %d-%dHz

", UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); + unsigned int bit; +#endif + + if (!uaudio_unit_getdesc(sc, id, units, &p, &subtype)) + return 0; + + /* + * find this unit on the list as it may be already processed as + * the source of another destination + */ + u = uaudio_unit_byid(sc, id); + if (u == NULL) { + u = malloc(sizeof(struct uaudio_unit), M_DEVBUF, M_WAITOK); + u->id = id; + u->type = subtype; + u->src_list = NULL; + u->dst_list = NULL; + u->clock = NULL; + u->mixent_list = NULL; + u->nch = 0; + u->name[0] = 0; + uaudio_ranges_init(&u->rates); + u->unit_next = sc->unit_list; + sc->unit_list = u; } else { - int r; - printf(" %d", UA_GETSAMP(asf1d, 0)); - for (r = 1; r < asf1d->bSamFreqType; r++) - printf(",%d", UA_GETSAMP(asf1d, r)); - printf("Hz

"); - } -#endif - ai.alt = id->bAlternateSetting; - ai.encoding = enc; - ai.attributes = sed->bmAttributes; - ai.idesc = id; - ai.edesc = ed; - ai.edesc1 = sync_ed; - ai.asf1desc = asf1d; - ai.sc_busy = 0; - if (sc->sc_nalts < UAUDIO_MAX_ALTS) - uaudio_add_alt(sc, &ai); -#ifdef UAUDIO_DEBUG - if (ai.attributes & UA_SED_FREQ_CONTROL) - DPRINTFN(1, ("%s: FREQ_CONTROL

", __func__)); - if (ai.attributes & UA_SED_PITCH_CONTROL) - DPRINTFN(1, ("%s: PITCH_CONTROL

", __func__)); -#endif - sc->sc_mode |= (dir == UE_DIR_OUT) ? AUMODE_PLAY : AUMODE_RECORD; - - return (USBD_NORMAL_COMPLETION); -} -#undef offs - -usbd_status -uaudio_identify_as(struct uaudio_softc *sc, - const usb_config_descriptor_t *cdesc) -{ - const usb_interface_descriptor_t *id; - const char *buf; - int size, offs; - - size = UGETW(cdesc->wTotalLength); - buf = (const char *)cdesc; - - /* Locate the AudioStreaming interface descriptor. */ - offs = 0; - id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOSTREAM, - sc->sc_quirks); - if (id == NULL) - return (USBD_INVAL); - - /* Loop through all the alternate settings. */ - while (offs <= size) { - DPRINTFN(2, ("%s: interface=%d offset=%d

", - __func__, id->bInterfaceNumber, offs)); - switch (id->bNumEndpoints) { - case 0: - DPRINTFN(2, ("%s: AS null alt=%d

", - __func__, id->bAlternateSetting)); - sc->sc_nullalt = id->bAlternateSetting; - break; - case 1: - case 2: - uaudio_process_as(sc, buf, &offs, size, id); - break; - default: - printf("%s: ignored audio interface with %d " - "endpoints

", - sc->sc_dev.dv_xname, id->bNumEndpoints); - break; - } - id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOSTREAM, - sc->sc_quirks); - if (id == NULL) - break; - } - if (offs > size) - return (USBD_INVAL); - DPRINTF(("%s: %d alts available

", __func__, sc->sc_nalts)); - - if (sc->sc_mode == 0) { - printf("%s: no usable endpoint found

", - sc->sc_dev.dv_xname); - return (USBD_INVAL); - } - - return (USBD_NORMAL_COMPLETION); -} - -usbd_status -uaudio_identify_ac(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) -{ - struct io_terminal* iot; - const usb_interface_descriptor_t *id; - const struct usb_audio_control_descriptor *acdp; - const usb_descriptor_t *dp; - const struct usb_audio_output_terminal *pot; - struct terminal_list *tml; - const char *buf, *ibuf, *ibufend; - int size, offs, aclen, ndps, i, j; - - size = UGETW(cdesc->wTotalLength); - buf = (char *)cdesc; - - /* Locate the AudioControl interface descriptor. */ - offs = 0; - id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOCONTROL, - sc->sc_quirks); - if (id == NULL) - return (USBD_INVAL); - if (offs + sizeof *acdp > size) - return (USBD_INVAL); - sc->sc_ac_iface = id->bInterfaceNumber; - DPRINTFN(2,("%s: AC interface is %d

", - __func__, sc->sc_ac_iface)); - - /* A class-specific AC interface header should follow. */ - ibuf = buf + offs; - acdp = (const struct usb_audio_control_descriptor *)ibuf; - if (acdp->bDescriptorType != UDESC_CS_INTERFACE || - acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER) - return (USBD_INVAL); - aclen = UGETW(acdp->wTotalLength); - if (offs + aclen > size) - return (USBD_INVAL); - - if (!(sc->sc_quirks & UAUDIO_FLAG_BAD_ADC) && - UGETW(acdp->bcdADC) != UAUDIO_VERSION) - return (USBD_INVAL); - - sc->sc_audio_rev = UGETW(acdp->bcdADC); - DPRINTFN(2,("%s: found AC header, vers=%03x, len=%d

", - __func__, sc->sc_audio_rev, aclen)); - - /* Some webcams descriptors advertise an off-by-one wTotalLength */ - if (sc->sc_quirks & UAUDIO_FLAG_BAD_ADC_LEN) - aclen++; - - sc->sc_nullalt = -1; - - /* Scan through all the AC specific descriptors */ - ibufend = ibuf + aclen; - dp = (const usb_descriptor_t *)ibuf; - ndps = 0; - iot = mallocarray(256, sizeof(struct io_terminal), - M_TEMP, M_NOWAIT | M_ZERO); - if (iot == NULL) { - printf("%s: no memory

", __func__); - return USBD_NOMEM; - } - for (;;) { - ibuf += dp->bLength; - if (ibuf >= ibufend) - break; - dp = (const usb_descriptor_t *)ibuf; - if (ibuf + dp->bLength > ibufend) { - free(iot, M_TEMP, 0); - return (USBD_INVAL); - } - if (dp->bDescriptorType != UDESC_CS_INTERFACE) { - printf("%s: skip desc type=0x%02x

", - __func__, dp->bDescriptorType); - continue; + switch (u->type) { + case UAUDIO_AC_CLKSRC: + case UAUDIO_AC_CLKSEL: + case UAUDIO_AC_CLKMULT: + case UAUDIO_AC_RATECONV: + /* not using 'dest' list */ + *rchild = u; + return 1; } - i = ((const struct usb_audio_input_terminal *)dp)->bTerminalId; - iot[i].d.desc = dp; - if (i > ndps) - ndps = i; - } - ndps++; - - /* construct io_terminal */ - for (i = 0; i < ndps; i++) { - dp = iot[i].d.desc; - if (dp == NULL) - continue; - if (dp->bDescriptorSubtype != UDESCSUB_AC_OUTPUT) - continue; - pot = iot[i].d.ot; - tml = uaudio_io_terminaltype(UGETW(pot->wTerminalType), iot, i); - free(tml, M_TEMP, 0); } -#ifdef UAUDIO_DEBUG - for (i = 0; i < 256; i++) { - if (iot[i].d.desc == NULL) - continue; - printf("id %d:\t", i); - switch (iot[i].d.desc->bDescriptorSubtype) { - case UDESCSUB_AC_INPUT: - printf("AC_INPUT type=%s

", uaudio_get_terminal_name - (UGETW(iot[i].d.it->wTerminalType))); - break; - case UDESCSUB_AC_OUTPUT: - printf("AC_OUTPUT type=%s ", uaudio_get_terminal_name - (UGETW(iot[i].d.ot->wTerminalType))); - printf("src=%d

", iot[i].d.ot->bSourceId); - break; - case UDESCSUB_AC_MIXER: - printf("AC_MIXER src="); - for (j = 0; j < iot[i].d.mu->bNrInPins; j++) - printf("%d ", iot[i].d.mu->baSourceId[j]); - printf("

"); - break; - case UDESCSUB_AC_SELECTOR: - printf("AC_SELECTOR src="); - for (j = 0; j < iot[i].d.su->bNrInPins; j++) - printf("%d ", iot[i].d.su->baSourceId[j]); - printf("

"); + if (dest) { + dest->dst_next = u->dst_list; + u->dst_list = dest; + if (dest->dst_next != NULL) { + /* already seen */ + *rchild = u; + return 1; + } + } + + switch (u->type) { + case UAUDIO_AC_INPUT: + if (!uaudio_getnum(&p, 2, &term)) + return 0; + if (!uaudio_getnum(&p, 1, &assoc)) + return 0; + switch (sc->version) { + case UAUDIO_V1: break; - case UDESCSUB_AC_FEATURE: - printf("AC_FEATURE src=%d

", iot[i].d.fu->bSourceId); + case UAUDIO_V2: + if (!uaudio_getnum(&p, 1, &clk)) + return 0; + if (!uaudio_process_unit(sc, NULL, + clk, units, &u->clock)) + return 0; break; - case UDESCSUB_AC_PROCESSING: - printf("AC_PROCESSING src="); - for (j = 0; j < iot[i].d.pu->bNrInPins; j++) - printf("%d ", iot[i].d.pu->baSourceId[j]); - printf("

"); + } + if (!uaudio_getnum(&p, 1, &u->nch)) + return 0; + uaudio_mkname(sc, uaudio_tname(term, 0), u->name); + DPRINTF("%02u: " + "in, nch = %d, term = 0x%x, assoc = %d

", + u->id, u->nch, term, assoc); + break; + case UAUDIO_AC_OUTPUT: + if (!uaudio_getnum(&p, 2, &term)) + return 0; + if (!uaudio_getnum(&p, 1, &assoc)) + return 0; + if (!uaudio_getnum(&p, 1, &id)) + return 0; + if (!uaudio_process_unit(sc, u, id, units, &s)) + return 0; + switch (sc->version) { + case UAUDIO_V1: break; - case UDESCSUB_AC_EXTENSION: - printf("AC_EXTENSION src="); - for (j = 0; j < iot[i].d.eu->bNrInPins; j++) - printf("%d ", iot[i].d.eu->baSourceId[j]); - printf("

"); + case UAUDIO_V2: + if (!uaudio_getnum(&p, 1, &clk)) + return 0; + if (!uaudio_process_unit(sc, NULL, + clk, units, &u->clock)) + return 0; + break; + } + u->src_list = s; + s->src_next = NULL; + u->nch = s->nch; + uaudio_mkname(sc, uaudio_tname(term, 1), u->name); + DPRINTF("%02u: " + "out, id = %d, nch = %d, term = 0x%x, assoc = %d

", + u->id, id, u->nch, term, assoc); + break; + case UAUDIO_AC_MIXER: + if (!uaudio_process_srcs(sc, u, units, &p)) + return 0; + if (!uaudio_process_nch(sc, u, &p)) + return 0; + DPRINTF("%02u: mixer, nch = %u:

", u->id, u->nch); + +#ifdef UAUDIO_DEBUG + /* + * Print the list of available mixer's unit knobs (a bit + * matrix). Matrix mixers are rare because levels are + * already controlled by feature units, making the mixer + * knobs redundant with the feature's knobs. So, for + * now, we don't add clutter to the mixer(4) interface + * and ignore all knobs. Other popular OSes doesn't + * seem to expose them either. + */ + bit = 0; + for (s = u->src_list; s != NULL; s = s->src_next) { + for (i = 0; i < s->nch; i++) { + for (j = 0; j < u->nch; j++) { + if ((bit++ & 7) == 0) { + if (!uaudio_getnum(&p, 1, &ctl)) + return 0; + } + if (ctl & 0x80) + DPRINTF("\t%02u[%d] -> [%d]

", + s->id, i, j); + ctl <<= 1; + } + } + } +#endif + break; + case UAUDIO_AC_SELECTOR: + /* + * Selectors are extreamly rare, so not supported yet. + */ + if (!uaudio_process_srcs(sc, u, units, &p)) + return 0; + if (u->src_list == NULL) { + printf("%s: selector %02u has no sources

", + DEVNAME(sc), u->id); + return 0; + } + u->nch = u->src_list->nch; + DPRINTF("%02u: selector, nch = %u

", u->id, u->nch); + break; + case UAUDIO_AC_FEATURE: + if (!uaudio_getnum(&p, 1, &id)) + return 0; + if (!uaudio_process_unit(sc, u, id, units, &s)) + return 0; + s->src_next = u->src_list; + u->src_list = s; + u->nch = s->nch; + switch (sc->version) { + case UAUDIO_V1: + if (!uaudio_getnum(&p, 1, &size)) + return 0; + break; + case UAUDIO_V2: + size = 4; break; - default: - printf("unknown audio control (subtype=%d)

", - iot[i].d.desc->bDescriptorSubtype); } - for (j = 0; j < iot[i].inputs_size; j++) { - int k; - printf("\tinput%d: ", j); - tml = iot[i].inputs[j]; - if (tml == NULL) { - printf("NULL

"); - continue; + DPRINTF("%02d: feature id = %d, nch = %d, size = %d

", + u->id, id, u->nch, size); + if (!uaudio_getnum(&p, size, &ctl)) + return 0; + ctl = uaudio_feature_fixup(sc, ctl); + for (i = 0; i < 16; i++) { + if ((ctl & 3) == 3) + uaudio_feature_addent(sc, u, i, -1); + ctl >>= 2; + } + for (j = 0; j < u->nch; j++) { + if (!uaudio_getnum(&p, size, &ctl)) + return 0; + ctl = uaudio_feature_fixup(sc, ctl); + for (i = 0; i < 16; i++) { + if ((ctl & 3) == 3) + uaudio_feature_addent(sc, u, i, j); + ctl >>= 2; } - for (k = 0; k < tml->size; k++) - printf("%s ", uaudio_get_terminal_name - (tml->terminals[k])); - printf("

"); } - printf("\toutput: "); - tml = iot[i].output; - for (j = 0; j < tml->size; j++) - printf("%s ", uaudio_get_terminal_name(tml->terminals[j])); - printf("

"); + break; + case UAUDIO_AC_EFFECT: + if (!uaudio_getnum(&p, 2, &type)) + return 0; + if (!uaudio_getnum(&p, 1, &id)) + return 0; + if (!uaudio_process_unit(sc, u, id, units, &s)) + return 0; + s->src_next = u->src_list; + u->src_list = s; + u->nch = s->nch; + DPRINTF("%02d: effect, type = %u, id = %d, nch = %d

", + u->id, type, id, u->nch); + break; + case UAUDIO_AC_PROCESSING: + case UAUDIO_AC_EXTENSION: + if (!uaudio_getnum(&p, 2, &type)) + return 0; + if (!uaudio_process_srcs(sc, u, units, &p)) + return 0; + if (!uaudio_process_nch(sc, u, &p)) + return 0; + DPRINTF("%02u: proc/ext, type = 0x%x, nch = %u

", + u->id, type, u->nch); + for (s = u->src_list; s != NULL; s = s->src_next) { + DPRINTF("%u:\tpin %u:

", u->id, s->id); + } + break; + case UAUDIO_AC_CLKSRC: + if (!uaudio_getnum(&p, 1, &attr)) + return 0; + if (!uaudio_getnum(&p, 1, &ctl)) + return 0; + DPRINTF("%02u: clock source, attr = 0x%x, ctl = 0x%x

", + u->id, attr, ctl); + uaudio_mkname(sc, uaudio_clkname(attr), u->name); + break; + case UAUDIO_AC_CLKSEL: + DPRINTF("%02u: clock sel

", u->id); + if (!uaudio_process_srcs(sc, u, units, &p)) + return 0; + if (u->src_list == NULL) { + printf("%s: clock selector %02u with no srcs

", + DEVNAME(sc), u->id); + return 0; + } + uaudio_mkname(sc, "clksel", u->name); + break; + case UAUDIO_AC_CLKMULT: + DPRINTF("%02u: clock mult

", u->id); + + /* XXX: fetch multiplier */ + printf("%s: clock multiplier not supported

", DEVNAME(sc)); + break; + case UAUDIO_AC_RATECONV: + DPRINTF("%02u: rate conv

", u->id); + + /* XXX: fetch multiplier */ + printf("%s: rate converter not supported

", DEVNAME(sc)); + break; } -#endif + if (rchild) + *rchild = u; + return 1; +} - for (i = 0; i < ndps; i++) { - dp = iot[i].d.desc; - if (dp == NULL) - continue; - DPRINTF(("%s: id=%d subtype=%d

", - __func__, i, dp->bDescriptorSubtype)); - switch (dp->bDescriptorSubtype) { - case UDESCSUB_AC_HEADER: - printf("%s: unexpected AC header

", __func__); - break; - case UDESCSUB_AC_INPUT: - uaudio_add_input(sc, iot, i); - break; - case UDESCSUB_AC_OUTPUT: - uaudio_add_output(sc, iot, i); +/* + * Try to set the unit name to the name of its destination terminal. If + * the name is ambigus (already given to another source unit or having + * multiple destinations) then return 0. + */ +int +uaudio_setname_dsts(struct uaudio_softc *sc, struct uaudio_unit *u, char *name) +{ + struct uaudio_unit *d = u; + + while (d != NULL) { + if (d->dst_list == NULL || d->dst_list->dst_next != NULL) break; - case UDESCSUB_AC_MIXER: - uaudio_add_mixer(sc, iot, i); + d = d->dst_list; + if (d->src_list == NULL || d->src_list->src_next != NULL) break; - case UDESCSUB_AC_SELECTOR: - uaudio_add_selector(sc, iot, i); + if (d->name[0] != '\0') { + if (name != NULL && strcmp(name, d->name) != 0) + break; + strlcpy(u->name, d->name, UAUDIO_NAMEMAX); + return 1; + } + } + return 0; +} + +/* + * Try to set the unit name to the name of its source terminal. If the + * name is ambigus (already given to another destination unit or + * having multiple sources) then return 0. + */ +int +uaudio_setname_srcs(struct uaudio_softc *sc, struct uaudio_unit *u, char *name) +{ + struct uaudio_unit *s = u; + + while (s != NULL) { + if (s->src_list == NULL || s->src_list->src_next != NULL) break; - case UDESCSUB_AC_FEATURE: - uaudio_add_feature(sc, iot, i); + s = s->src_list; + if (s->dst_list == NULL || s->dst_list->dst_next != NULL) break; - case UDESCSUB_AC_PROCESSING: - uaudio_add_processing(sc, iot, i); + if (s->name[0] != '\0') { + if (name != NULL && strcmp(name, s->name) != 0) + break; + strlcpy(u->name, s->name, UAUDIO_NAMEMAX); + return 1; + } + } + return 0; +} + +/* + * Set the name of the given unit by using both its source and + * destination units. This is naming scheme is only useful to units + * that would have ambigous names if only sources or only destination + * were used. + */ +void +uaudio_setname_middle(struct uaudio_softc *sc, struct uaudio_unit *u) +{ + struct uaudio_unit *s, *d; + char name[UAUDIO_NAMEMAX]; + + s = u->src_list; + while (1) { + if (s == NULL) { + DPRINTF("uaudio_setname_middle: %02u: has no srcs

", + u->id); + return; + } + if (s->name[0] != '\0') break; - case UDESCSUB_AC_EXTENSION: - uaudio_add_extension(sc, iot, i); + s = s->src_list; + } + + d = u->dst_list; + while (1) { + if (d == NULL) { + DPRINTF("uaudio_setname_middle: %02u: has no dests

", + u->id); + return; + } + if (d->name[0] != '\0') break; - default: - printf("%s: bad AC desc subtype=0x%02x

", - __func__, dp->bDescriptorSubtype); + d = d->dst_list; + } + + snprintf(name, UAUDIO_NAMEMAX, "%s_%s", d->name, s->name); + uaudio_mkname(sc, name, u->name); +} + +#ifdef UAUDIO_DEBUG +/* + * Return the synchronization type name, for debug purposes only. + */ +char * +uaudio_isoname(int isotype) +{ + switch (isotype) { + case UE_ISO_ASYNC: + return "async"; + case UE_ISO_ADAPT: + return "adapt"; + case UE_ISO_SYNC: + return "sync"; + default: + return "unk"; + } +} + +/* + * Return the name of the given mode, debug only + */ +char * +uaudio_modename(int mode) +{ + switch (mode) { + case 0: + return "none"; + case AUMODE_PLAY: + return "play"; + case AUMODE_RECORD: + return "rec"; + case AUMODE_PLAY | AUMODE_RECORD: + return "duplex"; + default: + return "unk"; + } +} + +/* + * Return UAC v2.0 endpoint usage, debug only + */ +char * +uaudio_usagename(int usage) +{ + switch (usage) { + case UE_ISO_USAGE_DATA: + return "data"; + case UE_ISO_USAGE_FEEDBACK: + return "feed"; + case UE_ISO_USAGE_IMPL: + return "impl"; + default: + return "unk"; + } +} + +/* + * Print a bitmap of rates on the console. + */ +void +uaudio_rates_print(int rates) +{ + unsigned int i; + + for (i = 0; i < UAUDIO_NRATES; i++) { + if (rates & (1 << i)) + printf(" %d", uaudio_rates[i]); + } + printf("

"); +} + + +/* + * Print uaudio_ranges to console. + */ +void +uaudio_ranges_print(struct uaudio_ranges *r) +{ + struct uaudio_ranges_el *e; + int more = 0; + + for (e = r->el; e != NULL; e = e->next) { + if (more) + printf(", "); + if (e->min == e->max) + printf("%d", e->min); + else + printf("[%d:%d]/%d", e->min, e->max, e->res); + more = 1; + } + printf(" (%d vals)

", r->nval); +} + +/* + * Print unit to the console. + */ +void +uaudio_print_unit(struct uaudio_softc *sc, struct uaudio_unit *u) +{ + struct uaudio_unit *s; + + switch (u->type) { + case UAUDIO_AC_INPUT: + printf("%02u: input <%s>, dest = %02u <%s>

", + u->id, u->name, u->dst_list->id, u->dst_list->name); + break; + case UAUDIO_AC_OUTPUT: + printf("%02u: output <%s>, source = %02u <%s>

", + u->id, u->name, u->src_list->id, u->src_list->name); + break; + case UAUDIO_AC_MIXER: + printf("%02u: mixer <%s>:

", u->id, u->name); + for (s = u->src_list; s != NULL; s = s->src_next) + printf("%02u:\tsource %u <%s>:

", + u->id, s->id, s->name); + break; + case UAUDIO_AC_SELECTOR: + printf("%02u: selector <%s>:

", u->id, u->name); + for (s = u->src_list; s != NULL; s = s->src_next) + printf("%02u:\tsource %u <%s>:

", + u->id, s->id, s->name); + break; + case UAUDIO_AC_FEATURE: + printf("%02u: feature <%s>, " + "src = %02u <%s>, dst = %02u <%s>, cls = %d

", + u->id, u->name, + u->src_list->id, u->src_list->name, + u->dst_list->id, u->dst_list->name, u->mixer_class); + break; + case UAUDIO_AC_EFFECT: + printf("%02u: effect <%s>, " + "src = %02u <%s>, dst = %02u <%s>

", + u->id, u->name, + u->src_list->id, u->src_list->name, + u->dst_list->id, u->dst_list->name); + break; + case UAUDIO_AC_PROCESSING: + case UAUDIO_AC_EXTENSION: + printf("%02u: proc/ext <%s>:

", u->id, u->name); + for (s = u->src_list; s != NULL; s = s->src_next) + printf("%02u:\tsource %u <%s>:

", + u->id, s->id, s->name); + break; + case UAUDIO_AC_CLKSRC: + printf("%02u: clock source <%s>

", u->id, u->name); + break; + case UAUDIO_AC_CLKSEL: + printf("%02u: clock sel <%s>

", u->id, u->name); + break; + case UAUDIO_AC_CLKMULT: + printf("%02u: clock mult

", u->id); + break; + case UAUDIO_AC_RATECONV: + printf("%02u: rate conv

", u->id); + break; + } +} + +/* + * Print the full mixer on the console. + */ +void +uaudio_mixer_print(struct uaudio_softc *sc) +{ + struct uaudio_mixent *m; + struct uaudio_unit *u; + + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + for (m = u->mixent_list; m != NULL; m = m->next) { + printf("%02u:\t%s.%s", + u->id, u->name, m->fname); + if (m->chan >= 0) + printf("[%u]", m->chan); + printf("

"); + } + } +} + +/* + * Print the full device configuration on the console. + */ +void +uaudio_conf_print(struct uaudio_softc *sc) +{ + struct