diff options
author | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2019-03-12 08:32:07 +0000 |
---|---|---|
committer | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2019-03-12 08:32:07 +0000 |
commit | 9d617a9576676e312af5111156aa05c9bf5b3459 (patch) | |
tree | 9805a23baecdf4893b7cc84401f6ef90e773c845 /sys/dev/usb/uaudio.c | |
parent | 47f6f1f8940b3e9b8ed428ea02b8595cdcbcad28 (diff) |
Add a new driver for USB Audio Class v2.0 devices. It replaces the
current one for UAC v1.0 devices.
The main difference with the old driver is that now we map audio
blocks to USB transfers, which allows precise synchronization and
reliability, including during low-latency operation.
with help from many, ok mpi
Diffstat (limited to 'sys/dev/usb/uaudio.c')
-rw-r--r-- | sys/dev/usb/uaudio.c | 6715 |
1 files changed, 3798 insertions, 2917 deletions
diff --git a/sys/dev/usb/uaudio.c b/sys/dev/usb/uaudio.c index e0cdbfd39ef..15796c46c07 100644 --- a/sys/dev/usb/uaudio.c +++ b/sys/dev/usb/uaudio.c @@ -1,3394 +1,4275 @@ -/* $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: uaudio.c,v 1.134 2019/03/12 08:32:06 ratchov Exp $ */ /* - * Copyright (c) 1999 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code is derived from software contributed to The NetBSD Foundation - * by Lennart Augustsson (lennart@augustsson.net) at - * Carlstedt Research & Technology. + * Copyright (c) 2018 Alexandre Ratchov <alex@caoua.org> * - * 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. + * 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 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 <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)) - -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 */ -}; +#define DEVNAME(sc) ((sc)->dev.dv_xname) + +/* + * Isochronous endpoint usage (XXX: these belong to dev/usb/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]; - - struct uaudio_softc *sc; /* our softc */ +/* + * Endpoint (UAC v1) or clock-source unit (UAC v2) sample rate control + */ +#define UAUDIO_REQSEL_RATE 0x01 + +/* + * Samples-per-frame are fractions. UAC v2.0 requires the denominator to + * be multiple of 2^16, as used in the sync pipe. On the othe hand, to + * represent sample-per-frame of all rates we support, we need the + * denominator to be such that (rate / 1000) can be represented exactly, + * 80 works. So we use the least common multiplier of both. + */ +#define UAUDIO_SPF_DIV 327680 + +/* + * 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)) - -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 */ -}; + /* + * 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; -#define UAC_OUTPUT 0 -#define UAC_INPUT 1 -#define UAC_EQUAL 2 -#define UAC_RECORD 3 -#define UAC_NCLASSES 4 -#ifdef UAUDIO_DEBUG -const char *uac_names[] = { - AudioCoutputs, AudioCinputs, AudioCequalization, AudioCrecord, + /* + * 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 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_MIN 2 +#define UAUDIO_NXFERS_MAX 8 + 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_MAX], sync_xfers[UAUDIO_NXFERS_MAX]; + + /* + * We don't use all the data_xfers[] entries because + * we can't schedule too many frames in the usb + * controller. + */ + unsigned int nxfers; + + unsigned int spf_remain; /* frac sample left */ + unsigned int spf; /* avg samples per frame */ + unsigned int spf_min, spf_max; /* allowed boundaries */ + + /* + * The max frame size we'll need (which may be lower + * than the maxpkt the usb pipe supports). + */ + unsigned int maxpkt; + + /* + * max number of frames per xfer we'll need + */ + unsigned int nframes_max; + + /* + * At usb2.0 speed, the number of (micro-)frames per + * transfer must correspond to 1ms, which is the usb1.1 + * frame duration. This is required by lower level usb + * drivers. + * + * The nframes_mask variable is used to test if the + * number of frames per transfer is usable (by checking + * that least significant bits are zero). For instance, + * nframes_mask will be set to 0x0 on usb1.1 device and + * 0x7 on usb2.0 devices running at 8000 fps. + */ + unsigned int nframes_mask; + + unsigned int data_nextxfer, sync_nextxfer; + struct usbd_pipe *data_pipe; + struct usbd_pipe *sync_pipe; + void (*intr)(void *); + void *arg; + + /* audio ring extents, passed to trigger() methods */ + unsigned char *ring_start, *ring_end; + + /* pointer to first byte available */ + unsigned char *ring_pos; + + /* audio(9) block size in bytes */ + int ring_blksz; + + /* xfer position relative to block boundary */ + int ring_offs; + + /* + * As USB sample-per-frame is not constant, we must + * schedule transfers slightly larger that one audio + * block. This is the "safe" block size, that ensures + * the transfer will cross the audio block boundary. + */ + int safe_blksz; + + /* + * Number of bytes completed, when it reaches a + * block size, we fire an audio(9) interrupt. + */ + int ring_icnt; + + /* + * USB transfers are used as a FIFO which is the + * concatenation of all transfers. This is the write + * (read) position of the play (rec) stream + */ + unsigned int ubuf_xfer; /* xfer index */ + unsigned int ubuf_pos; /* offset in bytes */ + } pstream, rstream; + + int ctl_ifnum; /* aka AC interface */ + + int mode; /* open() mode */ + int trigger_mode; /* trigger() mode */ + + unsigned int rate; /* current sample rate */ + unsigned int ufps; /* USB frames per second */ + unsigned int sync_pktsz; /* size of sync packet */ + unsigned int host_nframes; /* max frames we can schedule */ + + int diff_nsamp; /* samples play is ahead of rec */ + int diff_nframes; /* frames play is ahead of rec */ + unsigned int adjspf_age; /* frames since last uaudio_adjspf */ + + /* + * bytes pending to be copied to transfer buffer. This is play + * only, as recorded frames are copied as soon they are + * received. + */ + size_t copy_todo; }; -#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); +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 *); +void uaudio_copy_output(void *, size_t); +void uaudio_underrun(void *); +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 *uaudio_get_terminal_name(int); +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, same as if UAUDIO_DEBUG isn't defined + * 1 - initialisations & setup + * 2 - audio(4) calls + * 3 - transfers + */ +int uaudio_debug = 1; #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 *); -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; +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 */ + uaudio_copy_output, /* copy_output */ + uaudio_underrun /* underrun */ +}; - if (uaa->iface == NULL || uaa->device == NULL) - 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. + */ +int uaudio_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 64000, 88200, 96000, 128000, 176400, 192000 +}; - quirk = uaudio_lookup(uaa->vendor, uaa->product); - if (quirk) - flags = quirk->flags; +/* + * 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; - if (flags & UAUDIO_FLAG_BAD_AUDIO) - return (UMATCH_NONE); + s = 1 << (8 * opsize - 1); + return (val ^ s) - s; +} - id = usbd_get_interface_descriptor(uaa->iface); - if (id == NULL) - return (UMATCH_NONE); +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 usb_device_request r; + int err; - if (!(id->bInterfaceClass == UICLASS_AUDIO || - ((flags & UAUDIO_FLAG_VENDOR_CLASS) && - id->bInterfaceClass == UICLASS_VENDOR))) - return (UMATCH_NONE); + r.bmRequestType = type; + r.bRequest = req; + USETW(r.wValue, sel << 8 | chan); + USETW(r.wIndex, id << 8 | ifnum); + USETW(r.wLength, size); - if (id->bInterfaceSubClass != UISUBCLASS_AUDIOCONTROL) - return (UMATCH_NONE); + DPRINTF("%s: type = 0x%x, req = 0x%x, val = 0x%x, " + "index = 0x%x, size = %d\n", __func__, + type, req, UGETW(r.wValue), UGETW(r.wIndex), UGETW(r.wLength)); - cdesc = usbd_get_config_descriptor(uaa->device); - if (cdesc == NULL) - return (UMATCH_NONE); + err = usbd_do_request(sc->udev, &r, buf); + if (err) { + DPRINTF("%s: failed: %s\n", __func__, usbd_errstr(err)); + return 0; + } + return 1; +} - size = UGETW(cdesc->wTotalLength); - buf = (const char *)cdesc; +/* + * 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; - offs = 0; - cd_id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOSTREAM, - flags); - if (cd_id == NULL) - return (UMATCH_NONE); + if (p->wptr - p->rptr < size) { + DPRINTF("%s: %d: too small\n", __func__, size); + return 0; + } - offs = 0; - cd_id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOCONTROL, - flags); - if (cd_id == NULL) - return (UMATCH_NONE); + for (i = 0; i < size; i++) + num |= *p->rptr++ << (8 * i); - return (UMATCH_VENDOR_PRODUCT_CONF_IFACE); + if (ret) + *ret = num; + return 1; } -void -uaudio_attach(struct device *parent, struct device *self, void *aux) +/* + * Read a USB descriptor from the given blob. Return 0 on error. + */ +int +uaudio_getdesc(struct uaudio_blob *p, struct uaudio_blob *ret) { - 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\n", - sc->sc_dev.dv_xname); - return; - } + unsigned int size; - err = uaudio_identify(sc, cdesc); - if (err) { - printf("%s: audio descriptors make no sense, error=%d\n", - sc->sc_dev.dv_xname, err); - return; + if (!uaudio_getnum(p, 1, &size)) + return 0; + if (size-- == 0) { + DPRINTF("%s: zero sized desc\n", __func__); + return 0; } - - /* 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); + if (p->wptr - p->rptr < size) { + DPRINTF("%s: too small\n", __func__); + return 0; } + ret->rptr = p->rptr; + ret->wptr = p->rptr + size; + p->rptr += size; + return 1; +} - for (j = 0; j < sc->sc_nalts; j++) { - if (sc->sc_alts[j].ifaceh == NULL) { - printf("%s: alt %d missing AS interface(s)\n", - sc->sc_dev.dv_xname, j); - return; - } +/* + * 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) +{ + struct uaudio_unit *u; + + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + if (u->id == id) + break; } + return u; +} - printf("%s: audio rev %d.%02x", sc->sc_dev.dv_xname, - sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); +/* + * Return a terminal name for the given terminal type. + */ +char * +uaudio_tname(unsigned int type, int isout) +{ + unsigned int hi, lo; + char *name; - sc->sc_playchan.sc = sc->sc_recchan.sc = sc; - sc->sc_playchan.altidx = -1; - sc->sc_recchan.altidx = -1; + hi = type >> 8; + lo = type & 0xff; - if (sc->sc_quirks & UAUDIO_FLAG_NO_FRAC) - sc->sc_altflags |= UA_NOFRAC; + 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"; + } + return name; +} - printf(", %d mixer controls\n", sc->sc_nctls); +/* + * Return a clock name for the given clock type. + */ +char * +uaudio_clkname(unsigned int attr) +{ + static char *names[] = {"ext", "fixed", "var", "prog"}; - DPRINTF(("%s: doing audio_attach_mi\n", __func__)); - audio_attach_mi(&uaudio_hw_if, sc, &sc->sc_dev); + return names[attr & 3]; } -int -uaudio_detach(struct device *self, int flags) +/* + * Return an unique name for the given template. + */ +void +uaudio_mkname(struct uaudio_softc *sc, char *templ, char *res) { - struct uaudio_softc *sc = (struct uaudio_softc *)self; + struct uaudio_name *n; + char *sep; /* - * 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 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 */ - if (sc->sc_alts == NULL) - return (0); - return (config_detach_children(self, flags)); + 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++; } -const usb_interface_descriptor_t * -uaudio_find_iface(const char *buf, int size, int *offsp, int subtype, int flags) +/* + * Convert UAC v1.0 feature bitmap to UAC v2.0 feature bitmap. + */ +unsigned int +uaudio_feature_fixup(struct uaudio_softc *sc, unsigned int ctl) { - const usb_interface_descriptor_t *d; - - 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); + int i; + unsigned int bits, n; + + 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 (NULL); + return ctl; } +/* + * Initialize a uaudio_ranges to the empty set + */ void -uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct mixerctl *mc) +uaudio_ranges_init(struct uaudio_ranges *r) { - int res, range; - size_t len; - struct mixerctl *nmc; + r->el = NULL; + r->nval = 0; +} - if (mc->class < UAC_NCLASSES) { - DPRINTF(("%s: adding %s.%s\n", - __func__, uac_names[mc->class], mc->ctlname)); - } else { - DPRINTF(("%s: adding %s\n", __func__, mc->ctlname)); - } +/* + * 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; - nmc = mallocarray(sc->sc_nctls + 1, sizeof(*mc), M_USBDEV, M_NOWAIT); - if (nmc == NULL) { - printf("%s: no memory\n", __func__); + if (min > max) { + DPRINTF("%s: [%d:%d]/%d: bad range\n", __func__, + min, max, res); return; } - len = sizeof(*mc) * (sc->sc_nctls + 1); - /* 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)); + for (pe = &r->el; (e = *pe) != NULL; pe = &e->next) { + if (min <= e->max && max >= e->min) { + DPRINTF("%s: overlaping ranges\n", __func__); + return; + } + if (min < e->max) + break; } - 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; + /* XXX: use 'res' here */ + r->nval += max - min + 1; -#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\n", - mc->wIndex, mc->type, mc->ctlname, mc->ctlunit, - mc->minval, mc->maxval)); + 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_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)); } -#endif + r->nval = 0; } -uByte -uaudio_get_cluster_nchan(int id, const struct io_terminal *iot) +/* + * Convert a value in the given uaudio_ranges, into a 0..255 integer + * suitable for mixer usage + */ +int +uaudio_ranges_decode(struct uaudio_ranges *r, int val) { - struct usb_audio_cluster r; - const usb_descriptor_t *dp; - int i; + struct uaudio_ranges_el *e; + int diff, pos; - 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; + 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; } -bad: - printf("%s: bad data\n", __func__); - return (0); + return 0; } -void -uaudio_add_input(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) { -#ifdef UAUDIO_DEBUG - const struct usb_audio_input_terminal *d = iot[id].d.it; - - DPRINTFN(2,("%s: bTerminalId=%d wTerminalType=0x%04x " - "bAssocTerminal=%d bNrChannels=%d wChannelConfig=%d " - "iChannelNames=%d iTerminal=%d\n", - __func__, - d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, - d->bNrChannels, UGETW(d->wChannelConfig), - d->iChannelNames, d->iTerminal)); -#endif + struct uaudio_ranges_el *e; + int diff, pos; + + pos = (val * (r->nval - 1) + 127) / 255; + + for (e = r->el; e != NULL; e = e->next) { + diff = e->max - e->min + 1; + if (pos < diff) + return e->min + pos; + pos -= diff; + } + return 0; } -void -uaudio_add_output(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +/* + * 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) { -#ifdef UAUDIO_DEBUG - const struct usb_audio_output_terminal *d = iot[id].d.ot; + struct uaudio_ranges_el *e; + int rates, i, v; - DPRINTFN(2,("%s: bTerminalId=%d wTerminalType=0x%04x " - "bAssocTerminal=%d bSourceId=%d iTerminal=%d\n", - __func__, - d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, - d->bSourceId, d->iTerminal)); -#endif + rates = 0; + + for (e = r->el; e != NULL; e = e->next) { + for (i = 0; i < nitems(uaudio_rates); 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; + } + } + + return rates; } -void -uaudio_add_mixer(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +/* + * 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) { - 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; + int i, diff, best_index, best_diff; - DPRINTFN(2,("%s: bUnitId=%d bNrInPins=%d\n", __func__, - d->bUnitId, d->bNrInPins)); + best_index = -1; + best_diff = INT_MAX; + for (i = 0; i < nitems(uaudio_rates); 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; +} -#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 +/* + * 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; + + 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; - /* 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\n", __func__, ichs, ochs)); - - 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++; + /* count is at most 65535 */ + count = req_buf[0] | req_buf[1] << 8; + + /* 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); + + 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; +} + +/* + * 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("%s: no clock\n", __func__); + return 0; } - 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 */ } -#undef BIT - p += chs; } + return 0; +} +/* + * Return the rates bitmap of the given parameters setting + */ +int +uaudio_getrates(struct uaudio_softc *sc, struct uaudio_params *p) +{ + 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_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\n", __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_feature_addent(struct uaudio_softc *sc, + struct uaudio_unit *u, int uac_type, int chan) +{ + 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; + + if (uac_type >= sizeof(features) / sizeof(features[0])) { + printf("%s: skipped unknown feature\n", DEVNAME(sc)); + return; } - uaudio_mixer_add_ctl(sc, &mix); -} + 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)) { + printf("%s: failed to get ranges for %s control\n", + DEVNAME(sc), m->fname); + free(m, M_DEVBUF, sizeof(struct uaudio_mixent)); + return; + } + if (m->ranges.el == NULL) { + printf("%s: skipped %s control with empty range\n", + DEVNAME(sc), m->fname); + free(m, M_DEVBUF, sizeof(struct uaudio_mixent)); + return; + } #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; + 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\n", + 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]\n", m->fname, m->chan); } -#endif +/* + * For the given unit, parse the list of its sources and recursively + * call uaudio_process_unit() for each. + */ int -uaudio_determine_class(const struct io_terminal *iot, struct mixerctl *mix) +uaudio_process_srcs(struct uaudio_softc *sc, + struct uaudio_unit *u, struct uaudio_blob units, + struct uaudio_blob *p) { - int terminal_type; + struct uaudio_unit *s, **ps; + unsigned int i, npin, sid; - if (iot == NULL || iot->output == NULL) { - mix->class = UAC_OUTPUT; + if (!uaudio_getnum(p, 1, &npin)) 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 + 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; } - /* - * 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]; + return 1; +} + +/* + * 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; } + 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; + /* - * Otherwise, the class is UAC_OUTPUT. + * Find the usb descriptor for this id. */ - 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\n", __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\n", __func__, terminal_type)); - return AudioNmaster; + while (1) { + if (units.rptr == units.wptr) { + DPRINTF("%s: %02u: not found\n", __func__, 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; } -void -uaudio_add_feature(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +/* + * 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) { - 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_blob p; + struct uaudio_unit *u, *s; + unsigned int i, j, term, size, attr, ctl, type, subtype, assoc, clk; +#ifdef UAUDIO_DEBUG + unsigned int bit; +#endif -#define GET(i) (ctls[(i)*ctlsize] | \ - (ctlsize > 1 ? ctls[(i)*ctlsize+1] << 8 : 0)) + if (!uaudio_unit_getdesc(sc, id, units, &p, &subtype)) + return 0; - if (ctlsize == 0) { - DPRINTF(("ignoring feature %d: bControlSize == 0\n", id)); - return; + /* + * 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 { + 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; + } } - 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\n", - __func__, chan, GET(chan))); - cmask |= GET(chan); - } - - DPRINTFN(1,("%s: bUnitId=%d, " - "%d channels, mmask=0x%04x, cmask=0x%04x\n", - __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\n", - __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; + + if (dest) { + dest->dst_next = u->dst_list; + u->dst_list = dest; + if (dest->dst_next != NULL) { + /* already seen */ + *rchild = u; + return 1; } -#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); + } + + 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 VOLUME_CONTROL: - mix.type = MIX_SIGNED_16; - mix.ctlunit = AudioNvolume; - strlcpy(mix.ctlname, mixername, sizeof(mix.ctlname)); + 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 BASS_CONTROL: - mix.type = MIX_SIGNED_8; - mix.ctlunit = AudioNbass; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNbass); + } + 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\n", + 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 MID_CONTROL: - mix.type = MIX_SIGNED_8; - mix.ctlunit = AudioNmid; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNmid); + 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 TREBLE_CONTROL: - mix.type = MIX_SIGNED_8; - mix.ctlunit = AudioNtreble; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNtreble); + } + 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\n", + 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:\n", 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]\n", + 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\n", + DEVNAME(sc), u->id); + return 0; + } + u->nch = u->src_list->nch; + DPRINTF("%02u: selector, nch = %u\n", 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 GRAPHIC_EQUALIZER_CONTROL: - continue; /* XXX don't add anything */ + case UAUDIO_V2: + size = 4; break; - case AGC_CONTROL: - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), "%s.%s", - mixername, AudioNagc); + } + DPRINTF("%02d: feature id = %d, nch = %d, size = %d\n", + 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; + } + } + 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\n", + 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\n", + u->id, type, u->nch); + for (s = u->src_list; s != NULL; s = s->src_next) { + DPRINTF("%u:\tpin %u:\n", 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\n", + u->id, attr, ctl); + uaudio_mkname(sc, uaudio_clkname(attr), u->name); + break; + case UAUDIO_AC_CLKSEL: + DPRINTF("%02u: clock sel\n", 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\n", + DEVNAME(sc), u->id); + return 0; + } + uaudio_mkname(sc, "clksel", u->name); + break; + case UAUDIO_AC_CLKMULT: + DPRINTF("%02u: clock mult\n", u->id); + + /* XXX: fetch multiplier */ + printf("%s: clock multiplier not supported\n", DEVNAME(sc)); + break; + case UAUDIO_AC_RATECONV: + DPRINTF("%02u: rate conv\n", u->id); + + /* XXX: fetch multiplier */ + printf("%s: rate converter not supported\n", DEVNAME(sc)); + break; + } + if (rchild) + *rchild = u; + return 1; +} + +/* + * 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 DELAY_CONTROL: - mix.type = MIX_UNSIGNED_16; - mix.ctlunit = "4 ms"; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNdelay); + d = d->dst_list; + if (d->src_list == NULL || d->src_list->src_next != NULL) break; - case BASS_BOOST_CONTROL: - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNbassboost); + 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 LOUDNESS_CONTROL: - mix.type = MIX_ON_OFF; - mix.ctlunit = ""; - snprintf(mix.ctlname, sizeof(mix.ctlname), - "%s.%s", mixername, AudioNloudness); + s = s->src_list; + if (s->dst_list == NULL || s->dst_list->dst_next != NULL) break; + if (s->name[0] != '\0') { + if (name != NULL && strcmp(name, s->name) != 0) + break; + strlcpy(u->name, s->name, UAUDIO_NAMEMAX); + return 1; } - uaudio_mixer_add_ctl(sc, &mix); } + 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_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\n", - __func__, d->bUnitId, ud->bNrModes)); +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("%s: %02u: has no srcs\n", + __func__, u->id); + return; + } + if (s->name[0] != '\0') + break; + s = s->src_list; + } - if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { - DPRINTF(("%s: no mode select\n", __func__)); - return; + d = u->dst_list; + while (1) { + if (d == NULL) { + DPRINTF("%s: %02u: has no dests\n", + __func__, u->id); + return; + } + if (d->name[0] != '\0') + break; + d = d->dst_list; } - 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); + snprintf(name, UAUDIO_NAMEMAX, "%s_%s", d->name, s->name); + uaudio_mkname(sc, name, u->name); +} - for (i = 0; i < ud->bNrModes; i++) { - DPRINTFN(2,("%s: i=%d bm=0x%x\n", - __func__, i, UGETW(ud->waModes[i]))); - /* XXX */ +#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"; } - uaudio_mixer_add_ctl(sc, &mix); } -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\n", __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: +/* + * 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: - DPRINTF(("%s: unit %d, type=%d not impl.\n", - __func__, d->bUnitId, ptype)); - break; + return "unk"; } } -void -uaudio_add_extension(struct uaudio_softc *sc, const struct io_terminal *iot, int id) +/* + * Return UAC v2.0 endpoint usage, debug only + */ +char * +uaudio_usagename(int usage) { - 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; - - DPRINTFN(2,("%s: bUnitId=%d bNrInPins=%d\n", - __func__, d->bUnitId, d->bNrInPins)); - - if (sc->sc_quirks & UAUDIO_FLAG_NO_XU) - return; - - 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); - } -} - -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\n", __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\n", __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\n", __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\n", __func__); - return NULL; - } - tml = malloc(TERMINAL_LIST_SIZE(1), M_TEMP, M_NOWAIT); - if (tml == NULL) { - printf("%s: no memory\n", __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\n", __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\n", __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\n", __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\n", __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\n", __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\n", __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 uaudio_merge_terminal_list(it); - case UDESCSUB_AC_HEADER: + 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 NULL; + return "unk"; } } -usbd_status -uaudio_identify(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) +/* + * Print a bitmap of rates on the console. + */ +void +uaudio_rates_print(int rates) { - usbd_status err; + unsigned int i; - err = uaudio_identify_ac(sc, cdesc); - if (err) - return (err); - return (uaudio_identify_as(sc, cdesc)); + for (i = 0; i < nitems(uaudio_rates); i++) { + if (rates & (1 << i)) + printf(" %d", uaudio_rates[i]); + } + printf("\n"); } + +/* + * Print uaudio_ranges to console. + */ void -uaudio_add_alt(struct uaudio_softc *sc, const struct as_info *ai) +uaudio_ranges_print(struct uaudio_ranges *r) { - struct as_info *nai; - - nai = mallocarray(sc->sc_nalts + 1, sizeof(*ai), M_USBDEV, M_NOWAIT); - if (nai == NULL) { - printf("%s: no memory\n", __func__); - return; + 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)\n", r->nval); +} - /* 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\n", - __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\n", __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\n", - 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\n", - __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; - } - - 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\n", 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\n", - 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\n", - __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\n", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); - } - if (dir == UE_GET_DIR(sync_ed->bEndpointAddress)) { - printf("%s: sync ep wrong direction\n", - 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\n", - 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\n", - 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\n", - 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\n", - 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\n", - 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"; +/* + * 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>\n", + u->id, u->name, u->dst_list->id, u->dst_list->name); break; - case UA_FMT_PCM8: - enc = AUDIO_ENCODING_ULINEAR_LE; - sc->sc_altflags |= HAS_8U; - format_str = "pcm8"; + case UAUDIO_AC_OUTPUT: + printf("%02u: output <%s>, source = %02u <%s>\n", + u->id, u->name, u->src_list->id, u->src_list->name); break; - case UA_FMT_ALAW: - enc = AUDIO_ENCODING_ALAW; - sc->sc_altflags |= HAS_ALAW; - format_str = "alaw"; + case UAUDIO_AC_MIXER: + printf("%02u: mixer <%s>:\n", u->id, u->name); + for (s = u->src_list; s != NULL; s = s->src_next) + printf("%02u:\tsource %u <%s>:\n", + u->id, s->id, s->name); break; - case UA_FMT_MULAW: - enc = AUDIO_ENCODING_ULAW; - sc->sc_altflags |= HAS_MULAW; - format_str = "mulaw"; + case UAUDIO_AC_SELECTOR: + printf("%02u: selector <%s>:\n", u->id, u->name); + for (s = u->src_list; s != NULL; s = s->src_next) + printf("%02u:\tsource %u <%s>:\n", + u->id, s->id, s->name); + break; + case UAUDIO_AC_FEATURE: + printf("%02u: feature <%s>, " + "src = %02u <%s>, dst = %02u <%s>, cls = %d\n", + 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>\n", + 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>:\n", u->id, u->name); + for (s = u->src_list; s != NULL; s = s->src_next) + printf("%02u:\tsource %u <%s>:\n", + u->id, s->id, s->name); + break; + case UAUDIO_AC_CLKSRC: + printf("%02u: clock source <%s>\n", u->id, u->name); + break; + case UAUDIO_AC_CLKSEL: + printf("%02u: clock sel <%s>\n", u->id, u->name); + break; + case UAUDIO_AC_CLKMULT: + printf("%02u: clock mult\n", u->id); + break; + case UAUDIO_AC_RATECONV: + printf("%02u: rate conv\n", u->id); break; - case UA_FMT_IEEE_FLOAT: - default: - printf("%s: ignored setting with format %d\n", - sc->sc_dev.dv_xname, format); - return (USBD_NORMAL_COMPLETION); - } -#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\n", UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); - } else { - int r; - printf(" %d", UA_GETSAMP(asf1d, 0)); - for (r = 1; r < asf1d->bSamFreqType; r++) - printf(",%d", UA_GETSAMP(asf1d, r)); - printf("Hz\n"); - } -#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\n", __func__)); - if (ai.attributes & UA_SED_PITCH_CONTROL) - DPRINTFN(1, ("%s: PITCH_CONTROL\n", __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\n", - __func__, id->bInterfaceNumber, offs)); - switch (id->bNumEndpoints) { - case 0: - DPRINTFN(2, ("%s: AS null alt=%d\n", - __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\n", - 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\n", __func__, sc->sc_nalts)); - - if (sc->sc_mode == 0) { - printf("%s: no usable endpoint found\n", - 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\n", - __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\n", - __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\n", __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\n", - __func__, dp->bDescriptorType); - continue; - } - 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); +/* + * 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("\n"); + } } +} -#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\n", 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\n", 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("\n"); - 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("\n"); - break; - case UDESCSUB_AC_FEATURE: - printf("AC_FEATURE src=%d\n", iot[i].d.fu->bSourceId); - 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("\n"); - 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("\n"); +/* + * Print the full device configuration on the console. + */ +void +uaudio_conf_print(struct uaudio_softc *sc) +{ + struct uaudio_alt *a; + struct uaudio_params *p; + struct mixer_devinfo mi; + struct mixer_ctrl ctl; + int i, rates; + + mi.index = 0; + while (1) { + if (uaudio_query_devinfo(sc, &mi) != 0) break; - default: - printf("unknown audio control (subtype=%d)\n", - 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\n"); - continue; + + if (mi.type != AUDIO_MIXER_CLASS) { + ctl.dev = mi.index; + if (uaudio_get_port(sc, &ctl) != 0) { + printf("%02u: failed to get port\n", mi.index); + memset(&ctl.un, 0, sizeof(ctl.un)); } - for (k = 0; k < tml->size; k++) - printf("%s ", uaudio_get_terminal_name - (tml->terminals[k])); - printf("\n"); } - printf("\toutput: "); - tml = iot[i].output; - for (j = 0; j < tml->size; j++) - printf("%s ", uaudio_get_terminal_name(tml->terminals[j])); - printf("\n"); - } -#endif - for (i = 0; i < ndps; i++) { - dp = iot[i].d.desc; - if (dp == NULL) - continue; - DPRINTF(("%s: id=%d subtype=%d\n", - __func__, i, dp->bDescriptorSubtype)); - switch (dp->bDescriptorSubtype) { - case UDESCSUB_AC_HEADER: - printf("%s: unexpected AC header\n", __func__); - break; - case UDESCSUB_AC_INPUT: - uaudio_add_input(sc, iot, i); - break; - case UDESCSUB_AC_OUTPUT: - uaudio_add_output(sc, iot, i); - break; - case UDESCSUB_AC_MIXER: - uaudio_add_mixer(sc, iot, i); - break; - case UDESCSUB_AC_SELECTOR: - uaudio_add_selector(sc, iot, i); - break; - case UDESCSUB_AC_FEATURE: - uaudio_add_feature(sc, iot, i); - break; - case UDESCSUB_AC_PROCESSING: - uaudio_add_processing(sc, iot, i); + printf("%02u: <%s>, next = %d, prev = %d, class = %d", + mi.index, mi.label.name, mi.next, mi.prev, mi.mixer_class); + + switch (mi.type) { + case AUDIO_MIXER_CLASS: break; - case UDESCSUB_AC_EXTENSION: - uaudio_add_extension(sc, iot, i); + case AUDIO_MIXER_VALUE: + printf(", nch = %d, delta = %d", + mi.un.v.num_channels, mi.un.v.delta); + printf(", val ="); + for (i = 0; i < mi.un.v.num_channels; i++) + printf(" %d", ctl.un.value.level[i]); break; - default: - printf("%s: bad AC desc subtype=0x%02x\n", - __func__, dp->bDescriptorSubtype); + case AUDIO_MIXER_ENUM: + printf(", members:"); + for (i = 0; i != mi.un.e.num_mem; i++) { + printf(" %s(=%d)", + mi.un.e.member[i].label.name, + mi.un.e.member[i].ord); + } + printf(", val = %d", ctl.un.ord); break; } - } - /* delete io_terminal */ - for (i = 0; i < 256; i++) { - if (iot[i].d.desc == NULL) - continue; - if (iot[i].inputs != NULL) { - for (j = 0; j < iot[i].inputs_size; j++) - free(iot[i].inputs[j], M_TEMP, 0); - free(iot[i].inputs, M_TEMP, 0); - } - free(iot[i].output, M_TEMP, 0); - iot[i].d.desc = NULL; + printf("\n"); + mi.index++; } - free(iot, M_TEMP, 256 * sizeof(struct io_terminal)); - - return (USBD_NORMAL_COMPLETION); -} -int -uaudio_query_devinfo(void *addr, mixer_devinfo_t *mi) -{ - struct uaudio_softc *sc = addr; - struct mixerctl *mc; - int n, nctls, i; - - DPRINTFN(2,("%s: index=%d\n", __func__, mi->index)); - if (usbd_is_dying(sc->sc_udev)) - return (EIO); - - n = mi->index; - nctls = sc->sc_nctls; - - switch (n) { - case UAC_OUTPUT: - mi->type = AUDIO_MIXER_CLASS; - mi->mixer_class = UAC_OUTPUT; - mi->next = mi->prev = AUDIO_MIXER_LAST; - strlcpy(mi->label.name, AudioCoutputs, sizeof(mi->label.name)); - return (0); - case UAC_INPUT: - mi->type = AUDIO_MIXER_CLASS; - mi->mixer_class = UAC_INPUT; - mi->next = mi->prev = AUDIO_MIXER_LAST; - strlcpy(mi->label.name, AudioCinputs, sizeof(mi->label.name)); - return (0); - case UAC_EQUAL: - mi->type = AUDIO_MIXER_CLASS; - mi->mixer_class = UAC_EQUAL; - mi->next = mi->prev = AUDIO_MIXER_LAST; - strlcpy(mi->label.name, AudioCequalization, - sizeof(mi->label.name)); - return (0); - case UAC_RECORD: - mi->type = AUDIO_MIXER_CLASS; - mi->mixer_class = UAC_RECORD; - mi->next = mi->prev = AUDIO_MIXER_LAST; - strlcpy(mi->label.name, AudioCrecord, sizeof(mi->label.name)); - return 0; - default: - break; + printf("%d controls\n", mi.index); + + printf("alts:\n"); + for (a = sc->alts; a != NULL; a = a->next) { + rates = uaudio_alt_getrates(sc, a); + printf("mode = %s, ifnum = %d, altnum = %d, " + "addr = 0x%x, maxpkt = %d, sync = 0x%x, " + "nch = %d, fmt = s%dle%d, rates:", + uaudio_modename(a->mode), + a->ifnum, a->altnum, + a->data_addr, a->maxpkt, + a->sync_addr, + a->nch, a->bits, a->bps); + uaudio_rates_print(rates); } - n -= UAC_NCLASSES; - if (n < 0 || n >= nctls) - return (ENXIO); - - mc = &sc->sc_ctls[n]; - strlcpy(mi->label.name, mc->ctlname, sizeof(mi->label.name)); - mi->mixer_class = mc->class; - mi->next = mi->prev = AUDIO_MIXER_LAST; /* XXX */ - switch (mc->type) { - case MIX_ON_OFF: - mi->type = AUDIO_MIXER_ENUM; - mi->un.e.num_mem = 2; - strlcpy(mi->un.e.member[0].label.name, AudioNoff, - sizeof(mi->un.e.member[0].label.name)); - mi->un.e.member[0].ord = 0; - strlcpy(mi->un.e.member[1].label.name, AudioNon, - sizeof(mi->un.e.member[1].label.name)); - mi->un.e.member[1].ord = 1; - break; - case MIX_SELECTOR: - mi->type = AUDIO_MIXER_ENUM; - mi->un.e.num_mem = mc->maxval - mc->minval + 1; - for (i = 0; i <= mc->maxval - mc->minval; i++) { - snprintf(mi->un.e.member[i].label.name, - sizeof(mi->un.e.member[i].label.name), - "%d", i + mc->minval); - mi->un.e.member[i].ord = i + mc->minval; + printf("parameters:\n"); + for (p = sc->params_list; p != NULL; p = p->next) { + switch (sc->version) { + case UAUDIO_V1: + rates = p->v1_rates; + break; + case UAUDIO_V2: + rates = uaudio_getrates(sc, p); + break; } - break; - default: - mi->type = AUDIO_MIXER_VALUE; - strlcpy(mi->un.v.units.name, mc->ctlunit, - sizeof(mi->un.v.units.name)); - mi->un.v.num_channels = mc->nchan; - mi->un.v.delta = mc->delta; - break; + printf("pchan = %d, s%dle%d, rchan = %d, s%dle%d, rates:", + p->palt ? p->palt->nch : 0, + p->palt ? p->palt->bits : 0, + p->palt ? p->palt->bps : 0, + p->ralt ? p->ralt->nch : 0, + p->ralt ? p->ralt->bits : 0, + p->ralt ? p->ralt->bps : 0); + uaudio_rates_print(rates); } - return (0); } +#endif +/* + * Return the number of mixer controls that have the same name but + * control different channels of the same stream. + */ int -uaudio_open(void *addr, int flags) +uaudio_mixer_nchan(struct uaudio_mixent *m, struct uaudio_mixent **rnext) { - struct uaudio_softc *sc = addr; - - DPRINTF(("%s: sc=%p\n", __func__, sc)); - if (usbd_is_dying(sc->sc_udev)) - return (EIO); - - if ((flags & FWRITE) && !(sc->sc_mode & AUMODE_PLAY)) - return (ENXIO); - if ((flags & FREAD) && !(sc->sc_mode & AUMODE_RECORD)) - return (ENXIO); + char *name; + int i; - return (0); + i = 0; + name = m->fname; + while (m != NULL && strcmp(name, m->fname) == 0) { + m = m->next; + i++; + } + if (rnext) + *rnext = m; + return i; } /* - * Close function is called at splaudio(). + * Return pointer to the unit and mixer entry which have the given + * index exposed by the mixer(4) API. */ -void -uaudio_close(void *addr) +int +uaudio_mixer_byindex(struct uaudio_softc *sc, int index, + struct uaudio_unit **ru, struct uaudio_mixent **rm) { - struct uaudio_softc *sc = addr; + struct uaudio_unit *u; + struct uaudio_mixent *m; + char *name; + int i; - if (sc->sc_playchan.altidx != -1) - uaudio_chan_close(sc, &sc->sc_playchan); - if (sc->sc_recchan.altidx != -1) - uaudio_chan_close(sc, &sc->sc_recchan); + i = UAUDIO_CLASS_COUNT; + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + m = u->mixent_list; + while (1) { + if (m == NULL) + break; + if (index == i) { + *ru = u; + *rm = m; + return 1; + } + if (m->type == UAUDIO_MIX_NUM) { + name = m->fname; + while (m != NULL && + strcmp(name, m->fname) == 0) + m = m->next; + } else + m = m->next; + i++; + } + } + return 0; } +/* + * Parse AC header descriptor, we use it only to determine UAC + * version. Other properties (like wTotalLength) can be determined + * using other descriptors, so we try to no rely on them to avoid + * inconsistencies and the need for certain quirks. + */ int -uaudio_halt_out_dma(void *addr) +uaudio_process_header(struct uaudio_softc *sc, struct uaudio_blob *p) { - struct uaudio_softc *sc = addr; + struct uaudio_blob ph; + unsigned int type, subtype; - DPRINTF(("%s: enter\n", __func__)); - if (sc->sc_playchan.pipe != NULL) { - uaudio_chan_close(sc, &sc->sc_playchan); - sc->sc_playchan.pipe = NULL; - if (sc->sc_playchan.sync_pipe != NULL) - sc->sc_playchan.sync_pipe = NULL; - uaudio_chan_free_buffers(sc, &sc->sc_playchan); - sc->sc_playchan.intr = NULL; + if (!uaudio_getdesc(p, &ph)) + return 0; + if (!uaudio_getnum(&ph, 1, &type)) + return 0; + if (type != UDESC_CS_INTERFACE) { + DPRINTF("%s: expected cs iface desc\n", __func__); + return 0; + } + if (!uaudio_getnum(&ph, 1, &subtype)) + return 0; + if (subtype != UAUDIO_AC_HEADER) { + DPRINTF("%s: expected header desc\n", __func__); + return 0; } - return (0); + if (!uaudio_getnum(&ph, 2, &sc->version)) + return 0; + + DPRINTF("%s: version 0x%x\n", __func__, sc->version); + return 1; } +/* + * Process AC interrupt endpoint descriptor, this is mainly to skip + * the descriptor as we use neither of it's properties. Our mixer + * interface doesn't support unsolicitated state changes, so we've no + * use of it yet. + */ int -uaudio_halt_in_dma(void *addr) +uaudio_process_ac_ep(struct uaudio_softc *sc, struct uaudio_blob *p) { - struct uaudio_softc *sc = addr; +#ifdef UAUDIO_DEBUG + static const char *xfer[] = { + "ctl", "iso", "bulk", "intr" + }; +#endif + struct uaudio_blob dp; + unsigned int type, addr, attr, maxpkt, ival; + unsigned char *savepos; - DPRINTF(("%s: enter\n", __func__)); - if (sc->sc_recchan.pipe != NULL) { - uaudio_chan_close(sc, &sc->sc_recchan); - sc->sc_recchan.pipe = NULL; - if (sc->sc_recchan.sync_pipe != NULL) - sc->sc_recchan.sync_pipe = NULL; - uaudio_chan_free_buffers(sc, &sc->sc_recchan); - sc->sc_recchan.intr = NULL; + /* + * parse optional interrupt endpoint descriptor + */ + if (p->rptr == p->wptr) + return 1; + savepos = p->rptr; + if (!uaudio_getdesc(p, &dp)) + return 0; + if (!uaudio_getnum(&dp, 1, &type)) + return 0; + if (type != UDESC_ENDPOINT) { + p->rptr = savepos; + return 1; } - return (0); + + if (!uaudio_getnum(&dp, 1, &addr)) + return 0; + if (!uaudio_getnum(&dp, 1, &attr)) + return 0; + if (!uaudio_getnum(&dp, 2, &maxpkt)) + return 0; + if (!uaudio_getnum(&dp, 1, &ival)) + return 0; + + DPRINTF("%s: addr = 0x%x, type = %s, maxpkt = %d, ival = %d\n", + __func__, addr, xfer[UE_GET_XFERTYPE(attr)], + UE_GET_SIZE(maxpkt), ival); + + return 1; } /* - * Make sure the block size is large enough to hold at least 1 transfer. - * Ideally, the block size should be a multiple of the transfer size. - * Currently, the transfer size for play and record can differ, and there's - * no way to round playback and record blocksizes separately. + * Process the AC interface descriptors: mainly build the mixer and, + * for UAC v2.0, find the clock source. + * + * The audio device exposes an audio control (AC) interface with a big + * set of USB descriptors which expose the complete circuit the + * device. The circuit describes how the signal flows between the USB + * streaming interfaces to the terminal connectors (jacks, speakers, + * mics, ...). The circuit is build of mixers, source selectors, gain + * controls, mutters, processors, and alike; each comes with its own + * set of controls. Most of the boring driver work is to parse the + * circuit and build a human-usable set of controls that could be + * exposed through the mixer(4) interface. */ int -uaudio_round_blocksize(void *addr, int blk) +uaudio_process_ac(struct uaudio_softc *sc, struct uaudio_blob *p, int ifnum) { - struct uaudio_softc *sc = addr; - int bpf, pbpf, rbpf; + struct uaudio_blob units, pu; + struct uaudio_unit *u, *v; + unsigned char *savepos; + unsigned int type, subtype, id; + char *name, val; + + DPRINTF("%s: ifnum = %d, %zd bytes to processs\n", __func__, + ifnum, p->wptr - p->rptr); - DPRINTF(("%s: p.mbpf=%d r.mbpf=%d\n", __func__, - sc->sc_playchan.max_bytes_per_frame, - sc->sc_recchan.max_bytes_per_frame)); + sc->ctl_ifnum = ifnum; + + /* The first AC class-specific descriptor is the AC header */ + if (!uaudio_process_header(sc, p)) + return 0; - pbpf = rbpf = 0; - if (sc->sc_mode & AUMODE_PLAY) { - pbpf = (sc->sc_playchan.max_bytes_per_frame) * - sc->sc_playchan.nframes; + /* + * Determine the size of the AC descriptors array: scan + * descriptors until we get the first non-class-specific + * descriptor. This avoids relying on the wTotalLength field. + */ + savepos = p->rptr; + units.rptr = p->rptr; + while (p->rptr != p->wptr) { + if (!uaudio_getdesc(p, &pu)) + return 0; + if (!uaudio_getnum(&pu, 1, &type)) + return 0; + if (type != UDESC_CS_INTERFACE) + break; + units.wptr = p->rptr; } - if (sc->sc_mode & AUMODE_RECORD) { - rbpf = (sc->sc_recchan.max_bytes_per_frame) * - sc->sc_recchan.nframes; + p->rptr = savepos; + + /* + * Load units, walking from outputs to inputs, as + * the usb audio class spec requires. + */ + while (p->rptr != units.wptr) { + if (!uaudio_getdesc(p, &pu)) + return 0; + if (!uaudio_getnum(&pu, 1, &type)) + return 0; + if (!uaudio_getnum(&pu, 1, &subtype)) + return 0; + if (subtype == UAUDIO_AC_OUTPUT) { + if (!uaudio_getnum(&pu, 1, &id)) + return 0; + if (!uaudio_process_unit(sc, NULL, id, units, NULL)) + return 0; + } } - bpf = max(pbpf, rbpf); - if (blk < bpf) - blk = bpf; + /* + * set effect and processor unit names + */ + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + switch (u->type) { + case UAUDIO_AC_EFFECT: + uaudio_mkname(sc, "fx", u->name); + break; + case UAUDIO_AC_PROCESSING: + uaudio_mkname(sc, "proc", u->name); + break; + case UAUDIO_AC_EXTENSION: + uaudio_mkname(sc, "ext", u->name); + break; + } + } -#ifdef DIAGNOSTIC - if (blk <= 0) { - printf("%s: blk=%d\n", __func__, blk); - blk = 512; + /* + * set mixer/selector unit names + */ + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + if (u->type != UAUDIO_AC_MIXER && + u->type != UAUDIO_AC_SELECTOR) + continue; + if (!uaudio_setname_dsts(sc, u, NULL)) { + switch (u->type) { + case UAUDIO_AC_MIXER: + name = "mix"; + break; + case UAUDIO_AC_SELECTOR: + name = "sel"; + break; + } + uaudio_mkname(sc, name, u->name); + } + } + + /* + * set feature unit names and classes + */ + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + if (u->type != UAUDIO_AC_FEATURE) + continue; + if (uaudio_setname_dsts(sc, u, "record")) { + u->mixer_class = UAUDIO_CLASS_REC; + continue; + } + if (uaudio_setname_srcs(sc, u, "play")) { + u->mixer_class = UAUDIO_CLASS_OUT; + continue; + } + if (uaudio_setname_dsts(sc, u, NULL)) { + u->mixer_class = UAUDIO_CLASS_OUT; + continue; + } + if (uaudio_setname_srcs(sc, u, NULL)) { + u->mixer_class = UAUDIO_CLASS_IN; + continue; + } + uaudio_setname_middle(sc, u); + u->mixer_class = UAUDIO_CLASS_IN; + } + +#ifdef UAUDIO_DEBUG + if (uaudio_debug) { + printf("%s: units list:\n", DEVNAME(sc)); + for (u = sc->unit_list; u != NULL; u = u->unit_next) + uaudio_print_unit(sc, u); + + printf("%s: mixer controls:\n", DEVNAME(sc)); + uaudio_mixer_print(sc); } #endif - DPRINTFN(1,("%s: blk=%d\n", __func__, blk)); - return (blk); + /* follows optional interrupt endpoint descriptor */ + if (!uaudio_process_ac_ep(sc, p)) + return 0; + + /* fetch clock source rates */ + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + switch (u->type) { + case UAUDIO_AC_CLKSRC: + if (!uaudio_req_ranges(sc, 4, + UAUDIO_V2_REQSEL_CLKFREQ, + 0, /* channel (not used) */ + sc->ctl_ifnum, + u->id, + &u->rates)) { + printf("%s: failed to read clock rates\n", + DEVNAME(sc)); + return 1; + } +#ifdef UAUDIO_DEBUG + if (uaudio_debug) { + printf("%02u: clock rates: ", u->id); + uaudio_ranges_print(&u->rates); + } +#endif + break; + case UAUDIO_AC_CLKSEL: + if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE, + UAUDIO_V2_REQ_CUR, + UAUDIO_V2_REQSEL_CLKSEL, 0, + sc->ctl_ifnum, u->id, + &val, 1)) { + printf("%s: failed to read clock selector\n", + DEVNAME(sc)); + return 0; + } + for (v = u->src_list; v != NULL; v = v->src_next) { + if (--val == 0) + break; + } + u->clock = v; + break; + } + } + + if (sc->version == UAUDIO_V2) { + /* + * Find common clock unit. We assume all terminals + * belong to the same clock domain (ie are connected + * to the same source) + */ + sc->clock = NULL; + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + if (u->type != UAUDIO_AC_INPUT && + u->type != UAUDIO_AC_OUTPUT) + continue; + if (sc->clock == NULL) { + if (u->clock == NULL) { + printf("%s: terminal with no clock\n", + DEVNAME(sc)); + return 0; + } + sc->clock = u->clock; + } else if (u->clock != sc->clock) { + printf("%s: only one clock domain supported\n", + DEVNAME(sc)); + return 0; + } + } + + if (sc->clock == NULL) { + printf("%s: no clock found\n", DEVNAME(sc)); + return 0; + } + } + return 1; } +/* + * Parse endpoint descriptor with the following fromat: + * + * For playback there's a output data endpoint, of the + * following types: + * + * type sync descr + * ------------------------------------------------------- + * async: Yes the device uses it's own clock but + * sends feedback on a (input) sync endpoint + * for the host to adjust next packet size + * + * sync: - data rate is constant, and device + * is clocked to the usb bus. + * + * adapt: - the device adapts to data rate of the + * host. If fixed packet size is used, + * data rate is equivalent to the usb clock + * so this mode is the same as the + * sync mode. + * + * For recording there's and input data endpoint, of + * the following types: + * + * type sync descr + * ------------------------------------------------------- + * async: - the device uses its own clock and + * adjusts packet sizes. + * + * sync: - the device uses usb clock rate + * + * adapt: Yes the device uses host's feedback (on + * on a dedicated (output) sync endpoint + * to adapt to software's desired rate + * + * + * For usb1.1 ival is cardcoded to 1 for isochronous + * transfers, which means one transfer every ms. I.e one + * transfer every frame period. + * + * For usb2, ival the poll interval is: + * + * frame_period * 2^(ival - 1) + * + * so, if we use this formula, we get something working in all + * cases. + * + * The MaxPacketsOnly attribute is used only by "Type II" encodings, + * so we don't care about it. + */ int -uaudio_get_props(void *addr) +uaudio_process_as_ep(struct uaudio_softc *sc, + struct uaudio_blob *p, struct uaudio_alt *a, int nep) { - struct uaudio_softc *sc = addr; - int props = 0; - - if (!(sc->sc_quirks & UAUDIO_FLAG_DEPENDENT)) - props |= AUDIO_PROP_INDEPENDENT; + unsigned int addr, attr, maxpkt, isotype, ival; - if ((sc->sc_mode & (AUMODE_PLAY | AUMODE_RECORD)) == - (AUMODE_PLAY | AUMODE_RECORD)) - props |= AUDIO_PROP_FULLDUPLEX; + if (!uaudio_getnum(p, 1, &addr)) + return 0; + if (!uaudio_getnum(p, 1, &attr)) + return 0; + if (!uaudio_getnum(p, 2, &maxpkt)) + return 0; + if (!uaudio_getnum(p, 1, &ival)) /* bInterval */ + return 0; - return props; -} + DPRINTF("%s: addr = 0x%x, %s/%s, " + "maxpktsz = %d, ival = %d\n", + __func__, addr, + uaudio_isoname(UE_GET_ISO_TYPE(attr)), + uaudio_usagename(UE_GET_ISO_USAGE(attr)), + maxpkt, ival); -int -uaudio_get(struct uaudio_softc *sc, int which, int type, int wValue, - int wIndex, int len) -{ - usb_device_request_t req; - u_int8_t data[4]; - usbd_status err; - int val; - - if (wValue == -1) - return (0); - - req.bmRequestType = type; - req.bRequest = which; - USETW(req.wValue, wValue); - USETW(req.wIndex, wIndex); - USETW(req.wLength, len); - DPRINTFN(2,("%s: type=0x%02x req=0x%02x wValue=0x%04x " - "wIndex=0x%04x len=%d\n", - __func__, type, which, wValue, wIndex, len)); - err = usbd_do_request(sc->sc_udev, &req, data); - if (err) { - DPRINTF(("%s: err=%s\n", __func__, usbd_errstr(err))); - return (-1); - } - switch (len) { - case 1: - val = data[0]; - break; - case 2: - val = data[0] | (data[1] << 8); - break; - default: - DPRINTF(("%s: bad length=%d\n", __func__, len)); - return (-1); + if (UE_GET_XFERTYPE(attr) != UE_ISOCHRONOUS) { + printf("%s: skipped non-isoc endpt.\n", DEVNAME(sc)); + return 1; } - DPRINTFN(2,("%s: val=%d\n", __func__, val)); - return (val); -} -void -uaudio_set(struct uaudio_softc *sc, int which, int type, int wValue, - int wIndex, int len, int val) -{ - usb_device_request_t req; - u_int8_t data[4]; - usbd_status err; + /* + * For each AS interface setting, there's a single data + * endpoint and an optional feedback endpoint. The + * synchonization type is non-zero and must be set in the data + * endpoints. + * + * However, the isoc sync type field of the attribute can't be + * trusted: a lot of devices have it wrong. If the isoc sync + * type is set it's necessarily a data endpoint, if it's not, + * then if it is the only endpoint, it necessarily the data + * endpoint. + */ + isotype = UE_GET_ISO_TYPE(attr); + if (isotype || nep == 1) { + /* this is the data endpoint */ - if (wValue == -1) - return; + if (a->data_addr && addr != a->data_addr) { + printf("%s: skipped extra data endpt.\n", DEVNAME(sc)); + return 1; + } - req.bmRequestType = type; - req.bRequest = which; - USETW(req.wValue, wValue); - USETW(req.wIndex, wIndex); - USETW(req.wLength, len); - switch (len) { - case 1: - data[0] = val; - break; - case 2: - data[0] = val; - data[1] = val >> 8; - break; - default: - return; + a->mode = (UE_GET_DIR(addr) == UE_DIR_IN) ? + AUMODE_RECORD : AUMODE_PLAY; + a->data_addr = addr; + a->fps = sc->ufps / (1 << (ival - 1)); + a->maxpkt = UE_GET_SIZE(maxpkt); + } else { + /* this is the sync endpoint */ + + if (a->sync_addr && addr != a->sync_addr) { + printf("%s: skipped extra sync endpt.\n", DEVNAME(sc)); + return 1; + } + a->sync_addr = addr; } - DPRINTFN(2,("%s: type=0x%02x req=0x%02x wValue=0x%04x " - "wIndex=0x%04x len=%d, val=%d\n", __func__, - type, which, wValue, wIndex, len, val & 0xffff)); - err = usbd_do_request(sc->sc_udev, &req, data); -#ifdef UAUDIO_DEBUG - if (err) - DPRINTF(("%s: err=%d\n", __func__, err)); -#endif + + return 1; } +/* + * Parse AS general descriptor. Non-PCM interfaces are skipped. UAC + * v2.0 report the number of channels. For UAC v1.0 we set the number + * of channels to zero, it will be determined later from the format + * descriptor. + */ int -uaudio_signext(int type, int val) +uaudio_process_as_general(struct uaudio_softc *sc, + struct uaudio_blob *p, int *rispcm, struct uaudio_alt *a) { - if (!MIX_UNSIGNED(type)) { - if (MIX_SIZE(type) == 2) - val = (int16_t)val; - else - val = (int8_t)val; + unsigned int term, fmt, ctl, fmt_type, fmt_map, nch; + + if (!uaudio_getnum(p, 1, &term)) + return 0; + switch (sc->version) { + case UAUDIO_V1: + if (!uaudio_getnum(p, 1, NULL)) /* bDelay */ + return 0; + if (!uaudio_getnum(p, 1, &fmt)) + return 0; + *rispcm = (fmt == UAUDIO_V1_FMT_PCM); + break; + case UAUDIO_V2: + /* XXX: should we check if alt setting control is valid ? */ + if (!uaudio_getnum(p, 1, &ctl)) + return 0; + if (!uaudio_getnum(p, 1, &fmt_type)) + return 0; + if (!uaudio_getnum(p, 4, &fmt_map)) + return 0; + if (!uaudio_getnum(p, 1, &nch)) + return 0; + a->nch = nch; + *rispcm = (fmt_type == 1) && (fmt_map & UAUDIO_V2_FMT_PCM); } - return (val); + return 1; } +/* + * Parse AS format descriptor: we support only "Type 1" formats, aka + * PCM. Other formats are not really audio, they are data-only + * interfaces that we don't wan't to support: ethernet is much better + * for raw data transfers. + * + * XXX: handle ieee 754 32-bit floating point formats. + */ int -uaudio_unsignext(int type, int val) +uaudio_process_as_format(struct uaudio_softc *sc, + struct uaudio_blob *p, struct uaudio_alt *a, int *ispcm) { - if (!MIX_UNSIGNED(type)) { - if (MIX_SIZE(type) == 2) - val = (u_int16_t)val; - else - val = (u_int8_t)val; + unsigned int type, bps, bits, nch, nrates, rate_min, rate_max, rates; + int i, j; + + switch (sc->version) { + case UAUDIO_V1: + if (!uaudio_getnum(p, 1, &type)) + return 0; + if (type != 1) { + DPRINTF("%s: class v1: " + "skipped unsupported type = %d\n", __func__, type); + *ispcm = 0; + return 1; + } + if (!uaudio_getnum(p, 1, &nch)) + return 0; + if (!uaudio_getnum(p, 1, &bps)) + return 0; + if (!uaudio_getnum(p, 1, &bits)) + return 0; + if (!uaudio_getnum(p, 1, &nrates)) + return 0; + rates = 0; + if (nrates == 0) { + if (!uaudio_getnum(p, 3, &rate_min)) + return 0; + if (!uaudio_getnum(p, 3, &rate_max)) + return 0; + for (i = 0; i < nitems(uaudio_rates); i++) { + if (uaudio_rates[i] >= rate_min && + uaudio_rates[i] <= rate_max) + rates |= 1 << i; + } + } else { + for (j = 0; j < nrates; j++) { + if (!uaudio_getnum(p, 3, &rate_min)) + return 0; + for (i = 0; i < nitems(uaudio_rates); i++) { + if (uaudio_rates[i] == rate_min) + rates |= 1 << i; + } + } + } + a->v1_rates = rates; + a->nch = nch; + break; + case UAUDIO_V2: + /* + * sample rate ranges are obtained with requests to + * the clock source, as defined by the clock source + * descriptor + * + * the number of channels is in the GENERAL descriptor + */ + if (!uaudio_getnum(p, 1, &type)) + return 0; + if (type != 1) { + DPRINTF("%s: class v2: " + "skipped unsupported type = %d\n", __func__, type); + *ispcm = 0; + return 1; + } + if (!uaudio_getnum(p, 1, &bps)) + return 0; + if (!uaudio_getnum(p, 1, &bits)) + return 0; + + /* + * nch is in the v2 general desc, rates come from the + * clock source, so we're done. + */ + break; } - return (val); + a->bps = bps; + a->bits = bits; + *ispcm = 1; + return 1; } +/* + * Parse AS descriptors. + * + * The audio streaming (AS) interfaces are used to move data between + * the host and the device. On the one hand, the device has + * analog-to-digital (ADC) and digital-to-analog (DAC) converters + * which have their own low-jitter clock source. On other hand, the + * USB host runs a bus clock using another clock source. So both + * drift. That's why, the device sends feedback to the driver for the + * host to adjust continuously its data rate, hence the need for sync + * endpoints. + */ int -uaudio_value2bsd(struct mixerctl *mc, int val) -{ - int range; - DPRINTFN(5, ("%s: type=%03x val=%d min=%d max=%d ", - __func__, mc->type, val, mc->minval, mc->maxval)); - if (mc->type == MIX_ON_OFF) { - val = (val != 0); - } else if (mc->type == MIX_SELECTOR) { - if (val < mc->minval || val > mc->maxval) - val = mc->minval; - } else { - range = mc->maxval - mc->minval; - if (range == 0) - val = 0; - else - val = 255 * (uaudio_signext(mc->type, val) - - mc->minval) / range; +uaudio_process_as(struct uaudio_softc *sc, + struct uaudio_blob *p, int ifnum, int altnum, int nep) +{ + struct uaudio_alt *a, *anext, **pa; + struct uaudio_blob dp; + unsigned char *savep; + unsigned int type, subtype; + int ispcm = 0; + + a = malloc(sizeof(struct uaudio_alt), M_DEVBUF, M_WAITOK); + a->mode = 0; + a->nch = 0; + a->v1_rates = 0; + a->data_addr = 0; + a->sync_addr = 0; + a->ifnum = ifnum; + a->altnum = altnum; + + while (p->rptr != p->wptr) { + savep = p->rptr; + if (!uaudio_getdesc(p, &dp)) + goto failed; + if (!uaudio_getnum(&dp, 1, &type)) + goto failed; + if (type != UDESC_CS_INTERFACE) { + p->rptr = savep; + break; + } + if (!uaudio_getnum(&dp, 1, &subtype)) + goto failed; + switch (subtype) { + case UAUDIO_AS_GENERAL: + if (!uaudio_process_as_general(sc, &dp, &ispcm, a)) + goto failed; + break; + case UAUDIO_AS_FORMAT: + if (!uaudio_process_as_format(sc, &dp, a, &ispcm)) + goto failed; + break; + default: + DPRINTF("%s: unknown desc\n", __func__); + continue; + } + if (!ispcm) { + DPRINTF("%s: non-pcm iface\n", __func__); + free(a, M_DEVBUF, sizeof(struct uaudio_alt)); + return 1; + } } - DPRINTFN(5, ("val'=%d\n", val)); - return (val); -} -int -uaudio_bsd2value(struct mixerctl *mc, int val) -{ - DPRINTFN(5,("%s: type=%03x val=%d min=%d max=%d ", - __func__, mc->type, val, mc->minval, mc->maxval)); - if (mc->type == MIX_ON_OFF) { - val = (val != 0); - } else if (mc->type == MIX_SELECTOR) { - if (val < mc->minval || val > mc->maxval) - val = mc->minval; - } else - val = uaudio_unsignext(mc->type, - val * (mc->maxval - mc->minval) / 255 + mc->minval); - DPRINTFN(5, ("val'=%d\n", val)); - return (val); -} -int -uaudio_ctl_get(struct uaudio_softc *sc, int which, struct mixerctl *mc, - int chan) -{ - int val; + while (p->rptr != p->wptr) { + savep = p->rptr; + if (!uaudio_getdesc(p, &dp)) + goto failed; + if (!uaudio_getnum(&dp, 1, &type)) + goto failed; + if (type == UDESC_CS_ENDPOINT) + continue; + if (type != UDESC_ENDPOINT) { + p->rptr = savep; + break; + } + if (!uaudio_process_as_ep(sc, &dp, a, nep)) + goto failed; + } + + if (a->mode == 0) { + printf("%s: no data endpoints found\n", DEVNAME(sc)); + free(a, M_DEVBUF, sizeof(struct uaudio_alt)); + return 1; + } - DPRINTFN(5,("%s: which=%d chan=%d\n", __func__, which, chan)); - val = uaudio_get(sc, which, UT_READ_CLASS_INTERFACE, mc->wValue[chan], - mc->wIndex, MIX_SIZE(mc->type)); - return (uaudio_value2bsd(mc, val)); + /* + * Append to list of alts, but keep the list sorted by number + * of channels, bits and rate. From the most capable to the + * less capable. + */ + pa = &sc->alts; + while (1) { + if ((anext = *pa) == NULL) + break; + if (a->nch > anext->nch) + break; + else if (a->nch == anext->nch) { + if (a->bits > anext->bits) + break; + else if (sc->version == UAUDIO_V1 && + a->v1_rates > anext->v1_rates) + break; + } + pa = &anext->next; + } + a->next = *pa; + *pa = a; + return 1; +failed: + free(a, M_DEVBUF, sizeof(struct uaudio_alt)); + return 0; } +/* + * Populate the sc->params_list with combinations of play and rec alt + * settings that work together in full-duplex. + */ void -uaudio_ctl_set(struct uaudio_softc *sc, int which, struct mixerctl *mc, - int chan, int val) +uaudio_fixup_params(struct uaudio_softc *sc) { - val = uaudio_bsd2value(mc, val); - uaudio_set(sc, which, UT_WRITE_CLASS_INTERFACE, mc->wValue[chan], - mc->wIndex, MIX_SIZE(mc->type), val); -} + struct uaudio_alt *ap, *ar, *a; + struct uaudio_params *p, **pp; + int rates; -int -uaudio_mixer_get_port(void *addr, mixer_ctrl_t *cp) -{ - struct uaudio_softc *sc = addr; - struct mixerctl *mc; - int i, n, vals[MIX_MAX_CHAN], val; - - DPRINTFN(2,("%s: index=%d\n", __func__, cp->dev)); - - if (usbd_is_dying(sc->sc_udev)) - return (EIO); - - n = cp->dev - UAC_NCLASSES; - if (n < 0 || n >= sc->sc_nctls) - return (ENXIO); - mc = &sc->sc_ctls[n]; - - if (mc->type == MIX_ON_OFF) { - if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); - cp->un.ord = uaudio_ctl_get(sc, GET_CUR, mc, 0); - } else if (mc->type == MIX_SELECTOR) { - if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); - cp->un.ord = uaudio_ctl_get(sc, GET_CUR, mc, 0); - } else { - if (cp->type != AUDIO_MIXER_VALUE) - return (EINVAL); - if (cp->un.value.num_channels != 1 && - cp->un.value.num_channels != mc->nchan) - return (EINVAL); - for (i = 0; i < mc->nchan; i++) - vals[i] = uaudio_ctl_get(sc, GET_CUR, mc, i); - if (cp->un.value.num_channels == 1 && mc->nchan != 1) { - for (val = 0, i = 0; i < mc->nchan; i++) - val += vals[i]; - vals[0] = val / mc->nchan; + /* + * Add full-duplex parameter combinations. + */ + pp = &sc->params_list; + for (ap = sc->alts; ap != NULL; ap = ap->next) { + if (ap->mode != AUMODE_PLAY) + continue; + for (ar = sc->alts; ar != NULL; ar = ar->next) { + if (ar->mode != AUMODE_RECORD) + continue; + if (ar->bps != ap->bps || ar->bits != ap->bits) + continue; + switch (sc->version) { + case UAUDIO_V1: + rates = ap->v1_rates & ar->v1_rates; + if (rates == 0) + continue; + break; + case UAUDIO_V2: + /* UAC v2.0 common rates */ + rates = 0; + break; + } + p = malloc(sizeof(struct uaudio_params), + M_DEVBUF, M_WAITOK); + p->palt = ap; + p->ralt = ar; + p->v1_rates = rates; + p->next = NULL; + *pp = p; + pp = &p->next; } - for (i = 0; i < cp->un.value.num_channels; i++) - cp->un.value.level[i] = vals[i]; } - return (0); + /* + * For unidirectional devices, add play-only and or rec-only + * parameters. + */ + if (sc->params_list == NULL) { + for (a = sc->alts; a != NULL; a = a->next) { + p = malloc(sizeof(struct uaudio_params), + M_DEVBUF, M_WAITOK); + if (a->mode == AUMODE_PLAY) { + p->palt = a; + p->ralt = NULL; + } else { + p->palt = NULL; + p->ralt = a; + } + p->v1_rates = a->v1_rates; + p->next = NULL; + *pp = p; + pp = &p->next; + } + } } +/* + * Parse all descriptors and build configuration of the device. + */ int -uaudio_mixer_set_port(void *addr, mixer_ctrl_t *cp) -{ - struct uaudio_softc *sc = addr; - struct mixerctl *mc; - int i, n, vals[MIX_MAX_CHAN]; - - DPRINTFN(2,("%s: index = %d\n", __func__, cp->dev)); - if (usbd_is_dying(sc->sc_udev)) - return (EIO); - - n = cp->dev - UAC_NCLASSES; - if (n < 0 || n >= sc->sc_nctls) - return (ENXIO); - mc = &sc->sc_ctls[n]; - - if (mc->type == MIX_ON_OFF) { - if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); - uaudio_ctl_set(sc, SET_CUR, mc, 0, cp->un.ord); - } else if (mc->type == MIX_SELECTOR) { - if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); - uaudio_ctl_set(sc, SET_CUR, mc, 0, cp->un.ord); - } else { - if (cp->type != AUDIO_MIXER_VALUE) - return (EINVAL); - if (cp->un.value.num_channels == 1) - for (i = 0; i < mc->nchan; i++) - vals[i] = cp->un.value.level[0]; - else if (cp->un.value.num_channels == mc->nchan) - for (i = 0; i < mc->nchan; i++) - vals[i] = cp->un.value.level[i]; - else - return (EINVAL); - for (i = 0; i < mc->nchan; i++) - uaudio_ctl_set(sc, SET_CUR, mc, i, vals[i]); +uaudio_process_conf(struct uaudio_softc *sc, struct uaudio_blob *p) +{ + struct uaudio_blob dp; + unsigned int type, ifnum, altnum, nep, class, subclass; + int nac = 0; + + while (p->rptr != p->wptr) { + if (!uaudio_getdesc(p, &dp)) + return 0; + if (!uaudio_getnum(&dp, 1, &type)) + return 0; + if (type != UDESC_INTERFACE) + continue; + if (!uaudio_getnum(&dp, 1, &ifnum)) + return 0; + if (!uaudio_getnum(&dp, 1, &altnum)) + return 0; + if (!uaudio_getnum(&dp, 1, &nep)) + return 0; + if (!uaudio_getnum(&dp, 1, &class)) + return 0; + if (!uaudio_getnum(&dp, 1, &subclass)) + return 0; + if (class != UICLASS_AUDIO) { + DPRINTF("%s: skipped iface\n", __func__); + continue; + } + + switch (subclass) { + case UISUBCLASS_AUDIOCONTROL: + usbd_claim_iface(sc->udev, ifnum); + if (nac == 1) { + printf("%s: only one AC iface allowed\n", + DEVNAME(sc)); + return 0; + } + if (!uaudio_process_ac(sc, p, ifnum)) + return 0; + nac++; + break; + case UISUBCLASS_AUDIOSTREAM: + usbd_claim_iface(sc->udev, ifnum); + if (nep == 0) { + DPRINTF("%s: " + "stop altnum %d\n", __func__, altnum); + break; /* 0 is "stop sound", skip it */ + } + if (!uaudio_process_as(sc, p, ifnum, altnum, nep)) + return 0; + } } - return (0); + + uaudio_fixup_params(sc); + + return 1; } +/* + * Allocate a isochronous transfer and its bounce-buffers with the + * given maximum framesize and maximum frames per transfer. + */ int -uaudio_trigger_input(void *addr, void *start, void *end, int blksize, - void (*intr)(void *), void *arg, - struct audio_params *param) +uaudio_xfer_alloc(struct uaudio_softc *sc, struct uaudio_xfer *xfer, + unsigned int framesize, unsigned int count) { - struct uaudio_softc *sc = addr; - struct chan *ch = &sc->sc_recchan; - usbd_status err; - int i, s; + xfer->usb_xfer = usbd_alloc_xfer(sc->udev); + if (xfer->usb_xfer == NULL) + return ENOMEM; - if (usbd_is_dying(sc->sc_udev)) - return (EIO); + xfer->buf = usbd_alloc_buffer(xfer->usb_xfer, framesize * count); + if (xfer->buf == NULL) + return ENOMEM; - DPRINTFN(3,("%s: sc=%p start=%p end=%p " - "blksize=%d\n", __func__, sc, start, end, blksize)); + xfer->sizes = mallocarray(count, + sizeof(xfer->sizes[0]), M_DEVBUF, M_WAITOK); + if (xfer->sizes == NULL) + return ENOMEM; - uaudio_chan_set_param(ch, start, end, blksize); - DPRINTFN(3,("%s: sample_size=%d bytes/frame=%d " - "fraction=0.%03d\n", - __func__, ch->sample_size, ch->bytes_per_frame, - ch->fraction)); + return 0; +} - err = uaudio_chan_alloc_buffers(sc, ch); - if (err) - return (EIO); +/* + * Free a isochronous transfer and its bounce-buffers. + */ +void +uaudio_xfer_free(struct uaudio_softc *sc, struct uaudio_xfer *xfer, + unsigned int count) +{ + if (xfer->usb_xfer != NULL) { + /* frees request buffer as well */ + usbd_free_xfer(xfer->usb_xfer); + xfer->usb_xfer = NULL; + } + if (xfer->sizes != NULL) { + free(xfer->sizes, M_DEVBUF, + sizeof(xfer->sizes[0]) * count); + xfer->sizes = NULL; + } +} - err = uaudio_chan_open(sc, ch); - if (err) { - uaudio_chan_free_buffers(sc, ch); - return (EIO); +/* + * Close a stream and free all associated resources + */ +void +uaudio_stream_close(struct uaudio_softc *sc, int dir) +{ + struct uaudio_stream *s = &sc->pstream; + struct uaudio_alt *a = sc->params->palt; + struct usbd_interface *iface; + int err, i; + + if (dir == AUMODE_PLAY) { + s = &sc->pstream; + a = sc->params->palt; + } else { + s = &sc->rstream; + a = sc->params->ralt; } - ch->intr = intr; - ch->arg = arg; + if (s->data_pipe) { + usbd_close_pipe(s->data_pipe); + s->data_pipe = NULL; + } - s = splusb(); - for (i = 0; i < UAUDIO_NCHANBUFS; i++) - uaudio_chan_rtransfer(ch); - splx(s); + if (s->sync_pipe) { + usbd_close_pipe(s->sync_pipe); + s->sync_pipe = NULL; + } - return (0); + err = usbd_device2interface_handle(sc->udev, a->ifnum, &iface); + if (err) + printf("%s: can't get iface handle\n", DEVNAME(sc)); + else { + err = usbd_set_interface(iface, 0); + if (err) + printf("%s: can't reset interface\n", DEVNAME(sc)); + } + + for (i = 0; i < UAUDIO_NXFERS_MAX; i++) { + uaudio_xfer_free(sc, s->data_xfers + i, s->nframes_max); + uaudio_xfer_free(sc, s->sync_xfers + i, 1); + } } +/* + * Open a stream with the given buffer settings and set the current + * interface alt setting. + */ int -uaudio_trigger_output(void *addr, void *start, void *end, int blksize, - void (*intr)(void *), void *arg, - struct audio_params *param) +uaudio_stream_open(struct uaudio_softc *sc, int dir, + void *start, void *end, size_t blksz, void (*intr)(void *), void *arg) { - struct uaudio_softc *sc = addr; - struct chan *ch = &sc->sc_playchan; - usbd_status err; - int i, s; + struct uaudio_stream *s; + struct uaudio_alt *a; + struct usbd_interface *iface; + unsigned char req_buf[4]; + unsigned int bpa, spf_max, min_blksz; + int err, i; + + if (dir == AUMODE_PLAY) { + s = &sc->pstream; + a = sc->params->palt; + } else { + s = &sc->rstream; + a = sc->params->ralt; + } - if (usbd_is_dying(sc->sc_udev)) - return (EIO); + for (i = 0; i < UAUDIO_NXFERS_MAX; i++) { + s->data_xfers[i].usb_xfer = NULL; + s->data_xfers[i].sizes = NULL; + s->sync_xfers[i].usb_xfer = NULL; + s->sync_xfers[i].sizes = NULL; + } + s->data_pipe = NULL; + s->sync_pipe = NULL; + + s->nframes_mask = 0; + i = a->fps; + while (i > 1000) { + s->nframes_mask = (s->nframes_mask << 1) | 1; + i >>= 1; + } - DPRINTFN(3,("%s: sc=%p start=%p end=%p " - "blksize=%d\n", __func__, sc, start, end, blksize)); + /* bytes per audio frame */ + bpa = a->bps * a->nch; - uaudio_chan_set_param(ch, start, end, blksize); - DPRINTFN(3,("%s: sample_size=%d bytes/frame=%d " - "fraction=0.%03d\n", __func__, ch->sample_size, - ch->bytes_per_frame, ch->fraction)); + /* ideal samples per usb frame, fixed-point */ + s->spf = (uint64_t)sc->rate * UAUDIO_SPF_DIV / a->fps; - err = uaudio_chan_alloc_buffers(sc, ch); - if (err) - return (EIO); - - err = uaudio_chan_open(sc, ch); - if (err) { - uaudio_chan_free_buffers(sc, ch); - return (EIO); - } + /* + * UAC2.0 spec allows 1000PPM tolerance in sample frequency, + * while USB1.1 requires 1Hz, which is 125PPM at 8kHz. We + * accept as much as 1/256, which is 2500PPM. + */ + s->spf_min = (uint64_t)s->spf * 255 / 256; + s->spf_max = (uint64_t)s->spf * 257 / 256; - ch->intr = intr; - ch->arg = arg; + /* max spf can't exceed the device usb packet size */ + spf_max = (a->maxpkt / bpa) * UAUDIO_SPF_DIV; + if (s->spf_max > spf_max) + s->spf_max = spf_max; - s = splusb(); - for (i = 0; i < UAUDIO_NCHANBUFS; i++) - uaudio_chan_ptransfer(ch); - if (ch->sync_pipe) { - for (i = 0; i < UAUDIO_NSYNCBUFS; i++) - uaudio_chan_psync_transfer(ch); + /* + * Upon transfer completion the device must reach the audio + * block boundary, which is propagated to upper layers. In the + * worst case, we schedule only frames of spf_max samples, but + * the device returns only frames of spf_min samples; in this + * case the amount actually transfered is at least: + * + * min_blksz = blksz / spf_max * spf_min + * + * As we've UAUDIO_NXFERS outstanding blocks, worst-case + * remaining bytes is at most: + * + * UAUDIO_NXFERS * (blksz - min_blksz) + */ + min_blksz = (((uint64_t)blksz << 32) / s->spf_max * s->spf_max) >> 32; + + /* round to sample size */ + min_blksz -= min_blksz % bpa; + + /* finally this is what ensures we cross block boundary */ + s->safe_blksz = blksz + UAUDIO_NXFERS_MAX * (blksz - min_blksz); + + /* max number of (micro-)frames we'll ever use */ + s->nframes_max = (uint64_t)(s->safe_blksz / bpa) * + UAUDIO_SPF_DIV / s->spf_min + 1; + + /* round to next usb1.1 frame */ + s->nframes_max = (s->nframes_max + s->nframes_mask) & + ~s->nframes_mask; + + /* this is the max packet size we'll ever need */ + s->maxpkt = bpa * + ((s->spf_max + UAUDIO_SPF_DIV - 1) / UAUDIO_SPF_DIV); + + /* how many xfers we need to fill sc->host_nframes */ + s->nxfers = sc->host_nframes / s->nframes_max; + if (s->nxfers > UAUDIO_NXFERS_MAX) + s->nxfers = UAUDIO_NXFERS_MAX; + + DPRINTF("%s: %s: blksz = %zu, rate = %u, fps = %u\n", __func__, + dir == AUMODE_PLAY ? "play" : "rec", blksz, sc->rate, a->fps); + DPRINTF("%s: spf = 0x%x in [0x%x:0x%x]\n", __func__, + s->spf, s->spf_min, s->spf_max); + DPRINTF("%s: nframes_max = %u, nframes_mask = %u, maxpkt = %u\n", + __func__, s->nframes_max, s->nframes_mask, s->maxpkt); + DPRINTF("%s: safe_blksz = %d, nxfers = %d\n", __func__, + s->safe_blksz, s->nxfers); + + if (s->nxfers < UAUDIO_NXFERS_MIN) { + printf("%s: block size too large\n", DEVNAME(sc)); + return EIO; } - splx(s); - return (0); -} + /* + * Require at least 2ms block size to ensure no + * transfer exceeds two blocks. + * + * XXX: use s->nframes_mask instead of 1000 + */ + if (1000 * blksz < 2 * sc->rate * bpa) { + printf("%s: audio block too small\n", DEVNAME(sc)); + return EIO; + } -/* Set up a pipe for a channel. */ -usbd_status -uaudio_chan_open(struct uaudio_softc *sc, struct chan *ch) -{ - struct as_info *as = &sc->sc_alts[ch->altidx]; - int endpt = as->edesc->bEndpointAddress; - usbd_status err; + for (i = 0; i < s->nxfers; i++) { + err = uaudio_xfer_alloc(sc, s->data_xfers + i, + s->maxpkt, s->nframes_max); + if (err) + goto failed; + if (a->sync_addr) { + err = uaudio_xfer_alloc(sc, s->sync_xfers + i, + sc->sync_pktsz, 1); + if (err) + goto failed; + } + } - DPRINTF(("%s: endpt=0x%02x, speed=%d, alt=%d\n", - __func__, endpt, ch->sample_rate, as->alt)); + err = usbd_device2interface_handle(sc->udev, a->ifnum, &iface); + if (err) { + printf("%s: can't get iface handle\n", DEVNAME(sc)); + goto failed; + } - /* Set alternate interface corresponding to the mode. */ - err = usbd_set_interface(as->ifaceh, as->alt); + err = usbd_set_interface(iface, a->altnum); if (err) { - DPRINTF(("%s: usbd_set_interface failed\n", __func__)); - return (err); + printf("%s: can't set interface\n", DEVNAME(sc)); + goto failed; } /* - * If just one sampling rate is supported, - * no need to call uaudio_set_speed(). - * Roland SD-90 freezes by a SAMPLING_FREQ_CONTROL request. + * Set the sample rate. + * + * Certain devices are able to lock their clock to the data + * rate and expose no frequency control. In this case, the + * request to set the frequency will fail, but this error is + * safe to ignore. + * + * Such devices expose this capability in the class-specific + * endpoint descriptor (UAC v1.0) or in the clock unit + * descriptor (UAC v2.0) but we don't want to use them for now + * as certain devices have them wrong, missing or misplaced. */ - if (as->asf1desc->bSamFreqType != 1) { - err = uaudio_set_speed(sc, endpt, ch->sample_rate); - if (err) - DPRINTF(("%s: set_speed failed err=%s\n", - __func__, usbd_errstr(err))); + switch (sc->version) { + case UAUDIO_V1: + req_buf[0] = sc->rate; + req_buf[1] = sc->rate >> 8; + req_buf[2] = sc->rate >> 16; + if (!uaudio_req(sc, UT_WRITE_CLASS_ENDPOINT, + UAUDIO_V1_REQ_SET_CUR, UAUDIO_REQSEL_RATE, 0, + a->data_addr, 0, req_buf, 3)) { + DPRINTF("%s: not setting endpoint rate\n", __func__); + } + break; + case UAUDIO_V2: + req_buf[0] = sc->rate; + req_buf[1] = sc->rate >> 8; + req_buf[2] = sc->rate >> 16; + req_buf[3] = sc->rate >> 24; + if (!uaudio_req(sc, UT_WRITE_CLASS_INTERFACE, + UAUDIO_V2_REQ_CUR, UAUDIO_REQSEL_RATE, 0, + a->ifnum, sc->clock->id, req_buf, 4)) { + DPRINTF("%s: not setting clock rate\n", __func__); + } + break; } - if (sc->sc_quirks & UAUDIO_FLAG_EMU0202) - uaudio_set_speed_emu0202(ch); - - ch->pipe = 0; - ch->sync_pipe = 0; - DPRINTF(("%s: create pipe to 0x%02x\n", __func__, endpt)); - err = usbd_open_pipe(as->ifaceh, endpt, 0, &ch->pipe); + err = usbd_open_pipe(iface, a->data_addr, 0, &s->data_pipe); if (err) { - printf("%s: error creating pipe: err=%s endpt=0x%02x\n", - __func__, usbd_errstr(err), endpt); - return err; + printf("%s: can't open data pipe\n", DEVNAME(sc)); + goto failed; } - if (as->edesc1 != NULL) { - endpt = as->edesc1->bEndpointAddress; - DPRINTF(("%s: create sync-pipe to 0x%02x\n", __func__, endpt)); - err = usbd_open_pipe(as->ifaceh, endpt, 0, &ch->sync_pipe); + + if (a->sync_addr) { + err = usbd_open_pipe(iface, a->sync_addr, 0, &s->sync_pipe); if (err) { - printf("%s: error creating sync-pipe: err=%s endpt=0x%02x\n", - __func__, usbd_errstr(err), endpt); + printf("%s: can't open sync pipe\n", DEVNAME(sc)); + goto failed; } } - return err; + + s->data_nextxfer = 0; + s->sync_nextxfer = 0; + s->spf_remain = 0; + + s->intr = intr; + s->arg = arg; + s->ring_start = start; + s->ring_end = end; + s->ring_blksz = blksz; + + s->ring_pos = s->ring_start; + s->ring_offs = 0; + s->ring_icnt = 0; + + s->ubuf_xfer = 0; + s->ubuf_pos = 0; + return 0; + +failed: + uaudio_stream_close(sc, dir); + return ENOMEM; } +/* + * Adjust play samples-per-frame to keep play and rec streams in sync. + */ void -uaudio_chan_close(struct uaudio_softc *sc, struct chan *ch) +uaudio_adjspf(struct uaudio_softc *sc) { - struct as_info *as = &sc->sc_alts[ch->altidx]; + struct uaudio_stream *s = &sc->pstream; + int diff; - as->sc_busy = 0; - if (sc->sc_nullalt >= 0) { - DPRINTF(("%s: set null alt=%d\n", - __func__, sc->sc_nullalt)); - usbd_set_interface(as->ifaceh, sc->sc_nullalt); - } - if (ch->pipe) { - usbd_abort_pipe(ch->pipe); - usbd_close_pipe(ch->pipe); - } - if (ch->sync_pipe) { - usbd_abort_pipe(ch->sync_pipe); - usbd_close_pipe(ch->sync_pipe); + if (sc->mode != (AUMODE_RECORD | AUMODE_PLAY)) + return; + if (s->sync_pipe != NULL) + return; + + /* + * number of samples play stream is ahead of record stream. + */ + diff = sc->diff_nsamp; + if (sc->diff_nframes > 0) { + diff -= (uint64_t)sc->pstream.spf * + sc->diff_nframes / UAUDIO_SPF_DIV; + } else { + diff += (uint64_t)sc->rstream.spf * + -sc->diff_nframes / UAUDIO_SPF_DIV; } + + /* + * adjust samples-per-frames to resync within the next second + */ + s->spf = (uint64_t)(sc->rate - diff) * UAUDIO_SPF_DIV / sc->ufps; + if (s->spf > s->spf_max) + s->spf = s->spf_max; + if (s->spf < s->spf_min) + s->spf = s->spf_min; +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 2) + printf("%s: diff = %d, spf = 0x%x\n", __func__, diff, s->spf); +#endif } -usbd_status -uaudio_chan_alloc_buffers(struct uaudio_softc *sc, struct chan *ch) +/* + * Copy one audio block to the xfer buffer. + */ +void +uaudio_pdata_copy(struct uaudio_softc *sc) { - struct as_info *as = &sc->sc_alts[ch->altidx]; - struct usbd_xfer *xfer; - void *buf; - int i, size; - - DPRINTF(("%s: max_bytes_per_frame=%d nframes=%d\n", __func__, - ch->max_bytes_per_frame, ch->nframes)); + struct uaudio_stream *s = &sc->pstream; + struct uaudio_xfer *xfer; + size_t count, avail; +#ifdef UAUDIO_DEBUG + struct timeval tv; - size = ch->max_bytes_per_frame * ch->nframes; - for (i = 0; i < UAUDIO_NCHANBUFS; i++) { - xfer = usbd_alloc_xfer(sc->sc_udev); - if (xfer == 0) - goto bad; - ch->chanbufs[i].xfer = xfer; - buf = usbd_alloc_buffer(xfer, size); - if (buf == 0) { - i++; - goto bad; - } - ch->chanbufs[i].buffer = buf; - ch->chanbufs[i].chan = ch; - } - if (as->edesc1 != NULL) { - size = (ch->hi_speed ? 4 : 3) * ch->nsync_frames; - for (i = 0; i < UAUDIO_NSYNCBUFS; i++) { - xfer = usbd_alloc_xfer(sc->sc_udev); - if (xfer == 0) - goto bad_sync; - ch->syncbufs[i].xfer = xfer; - buf = usbd_alloc_buffer(xfer, size); - if (buf == 0) { - i++; - goto bad_sync; - } - ch->syncbufs[i].buffer = buf; - ch->syncbufs[i].chan = ch; + getmicrotime(&tv); +#endif + xfer = s->data_xfers + s->ubuf_xfer; + while (sc->copy_todo > 0) { + avail = s->ring_end - s->ring_pos; + count = xfer->size - s->ubuf_pos; + if (count > avail) + count = avail; + if (count > sc->copy_todo) + count = sc->copy_todo; +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 2) { + printf("%s: %llu.%06lu: %zd..%zd -> %u:%u..%zu\n", + __func__, tv.tv_sec, tv.tv_usec, + s->ring_pos - s->ring_start, + s->ring_pos - s->ring_start + count, + s->ubuf_xfer, s->ubuf_pos, s->ubuf_pos + count); + } +#endif + memcpy(xfer->buf + s->ubuf_pos, s->ring_pos, count); + sc->copy_todo -= count; + s->ring_pos += count; + if (s->ring_pos == s->ring_end) { + s->ring_pos = s->ring_start; + } + s->ubuf_pos += count; + if (s->ubuf_pos == xfer->size) { + s->ubuf_pos = 0; + s->ubuf_xfer++; + if (s->ubuf_xfer == s->nxfers) + s->ubuf_xfer = 0; + if (s->ubuf_xfer == s->data_nextxfer) + break; + xfer = s->data_xfers + s->ubuf_xfer; } } +} - return (USBD_NORMAL_COMPLETION); +/* + * Calculate and fill xfer frames sizes. + */ +void +uaudio_pdata_calcsizes(struct uaudio_softc *sc, struct uaudio_xfer *xfer) +{ +#ifdef UAUDIO_DEBUG + struct timeval tv; +#endif + struct uaudio_stream *s = &sc->pstream; + struct uaudio_alt *a = sc->params->palt; + unsigned int fsize, bpf; + int done; + + bpf = a->bps * a->nch; + done = s->ring_offs; + xfer->nframes = 0; + + while (1) { + /* + * if we crossed the next block boundary, we're done + */ + if ((xfer->nframes & s->nframes_mask) == 0 && + done > s->safe_blksz) + break; + + /* + * this can't happen, debug only + */ + if (xfer->nframes == s->nframes_max) { + printf("%s: too many frames for play xfer: " + "done = %u, blksz = %d\n", + DEVNAME(sc), done, s->ring_blksz); + break; + } -bad: - while (--i >= 0) - /* implicit buffer free */ - usbd_free_xfer(ch->chanbufs[i].xfer); - return (USBD_NOMEM); + /* + * calculate frame size and adjust state + */ + s->spf_remain += s->spf; + fsize = s->spf_remain / UAUDIO_SPF_DIV * bpf; + s->spf_remain %= UAUDIO_SPF_DIV; + done += fsize; + xfer->sizes[xfer->nframes] = fsize; + xfer->nframes++; + } -bad_sync: - while (--i >= 0) - /* implicit buffer free */ - usbd_free_xfer(ch->syncbufs[i].xfer); - return (USBD_NOMEM); + xfer->size = done - s->ring_offs; + s->ring_offs = done - s->ring_blksz; +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 3) { + getmicrotime(&tv); + printf("%s: size = %d, offs -> %d\n", __func__, + xfer->size, s->ring_offs); + } +#endif + memset(xfer->buf, 0, xfer->size); } +/* + * Submit a play data transfer to the USB driver. + */ void -uaudio_chan_free_buffers(struct uaudio_softc *sc, struct chan *ch) +uaudio_pdata_xfer(struct uaudio_softc *sc) { - struct as_info *as = &sc->sc_alts[ch->altidx]; - int i; +#ifdef UAUDIO_DEBUG + struct timeval tv; +#endif + struct uaudio_stream *s = &sc->pstream; + struct uaudio_xfer *xfer; + int err; + + xfer = s->data_xfers + s->data_nextxfer; - for (i = 0; i < UAUDIO_NCHANBUFS; i++) - usbd_free_xfer(ch->chanbufs[i].xfer); - if (as->edesc1 != NULL) { - for (i = 0; i < UAUDIO_NSYNCBUFS; i++) - usbd_free_xfer(ch->syncbufs[i].xfer); +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 3) { + getmicrotime(&tv); + printf("%s: %llu.%06lu: " + "%d bytes, %u frames, remain = 0x%x, offs = %d\n", + __func__, tv.tv_sec, tv.tv_usec, + xfer->size, xfer->nframes, + s->spf_remain, s->ring_offs); + } +#endif + + /* this can't happen, debug only */ + if (xfer->nframes == 0) { + printf("%s: zero frame play xfer\n", DEVNAME(sc)); + return; } + + /* + * We accept short transfers because in case of babble/stale frames + * the tranfer will be short + */ + usbd_setup_isoc_xfer(xfer->usb_xfer, s->data_pipe, sc, + xfer->sizes, xfer->nframes, + USBD_NO_COPY | USBD_SHORT_XFER_OK, + uaudio_pdata_intr); + + err = usbd_transfer(xfer->usb_xfer); + if (err != 0 && err != USBD_IN_PROGRESS) + printf("%s: play xfer, err = %d\n", DEVNAME(sc), err); + + if (++s->data_nextxfer == s->nxfers) + s->data_nextxfer = 0; + } -/* Called at splusb() */ +/* + * Callback called by the USB driver upon completion of play data transfer. + */ void -uaudio_chan_ptransfer(struct chan *ch) +uaudio_pdata_intr(struct usbd_xfer *usb_xfer, void *arg, usbd_status status) { - struct chanbuf *cb; - u_char *pos; - int i, n, size, residue, total; +#ifdef UAUDIO_DEBUG + struct timeval tv; +#endif + struct uaudio_softc *sc = arg; + struct uaudio_stream *s = &sc->pstream; + struct uaudio_xfer *xfer; + uint32_t size; + int nintr; + + if (status != 0) { + DPRINTF("%s: xfer status = %d\n", __func__, status); + return; + } - if (usbd_is_dying(ch->sc->sc_udev)) + xfer = s->data_xfers + s->data_nextxfer; + if (xfer->usb_xfer != usb_xfer) { + DPRINTF("%s: wrong xfer\n", __func__); return; + } - /* Pick the next channel buffer. */ - cb = &ch->chanbufs[ch->curchanbuf]; - if (++ch->curchanbuf >= UAUDIO_NCHANBUFS) - ch->curchanbuf = 0; + sc->diff_nsamp += xfer->size / + (sc->params->palt->nch * sc->params->palt->bps); + sc->diff_nframes += xfer->nframes; - /* Compute the size of each frame in the next transfer. */ - residue = ch->residue; - total = 0; - for (i = 0; i < ch->nframes; i++) { - size = ch->bytes_per_frame; - residue += ch->fraction; - if (residue >= ch->frac_denom) { - if ((ch->sc->sc_altflags & UA_NOFRAC) == 0) - size += ch->sample_size; - residue -= ch->frac_denom; - } - cb->sizes[i] = size; - total += size; +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 2) { + getmicrotime(&tv); + printf("%s: %llu.%06lu: %u: %u bytes\n", + __func__, tv.tv_sec, tv.tv_usec, + s->data_nextxfer, xfer->size); + } +#endif + usbd_get_xfer_status(usb_xfer, NULL, NULL, &size, NULL); + if (size != xfer->size) { + printf("%s: %u bytes out of %u: incomplete play xfer\n", + DEVNAME(sc), size, xfer->size); } - ch->residue = residue; - cb->size = total; /* - * Transfer data from upper layer buffer to channel buffer. Be sure - * to let the upper layer know each time a block is moved, so it can - * add more. + * Upper layer call-back may call uaudio_underrun(), which + * needs the current size of this transfer. So, don't + * recalculate the sizes and don't schedule the transfer yet. */ - pos = cb->buffer; - while (total > 0) { - n = min(total, ch->end - ch->cur); - n = min(n, ch->blksize - ch->transferred); - memcpy(pos, ch->cur, n); - total -= n; - pos += n; - ch->cur += n; - if (ch->cur >= ch->end) - ch->cur = ch->start; - - ch->transferred += n; - /* Call back to upper layer */ - if (ch->transferred >= ch->blksize) { - DPRINTFN(5,("%s: call %p(%p)\n", - __func__, ch->intr, ch->arg)); - mtx_enter(&audio_lock); - ch->intr(ch->arg); - mtx_leave(&audio_lock); - ch->transferred -= ch->blksize; - } + s->ring_icnt += xfer->size; + nintr = 0; + mtx_enter(&audio_lock); + while (s->ring_icnt >= s->ring_blksz) { + s->intr(s->arg); + s->ring_icnt -= s->ring_blksz; + nintr++; } + mtx_leave(&audio_lock); + if (nintr != 1) + printf("%s: %d: bad play intr count\n", __func__, nintr); + uaudio_pdata_calcsizes(sc, xfer); + uaudio_pdata_xfer(sc); +} + +/* + * Submit a play sync transfer to the USB driver. + */ +void +uaudio_psync_xfer(struct uaudio_softc *sc) +{ #ifdef UAUDIO_DEBUG - if (uaudiodebug > 8) { - DPRINTF(("%s: buffer=%p, residue=0.%03d\n", - __func__, cb->buffer, ch->residue)); - for (i = 0; i < ch->nframes; i++) { - DPRINTF((" [%d] length %d\n", i, cb->sizes[i])); - } - } + struct timeval tv; +#endif + struct uaudio_stream *s = &sc->pstream; + struct uaudio_xfer *xfer; + unsigned int i; + int err; + + xfer = s->sync_xfers + s->sync_nextxfer; + xfer->nframes = 1; + + for (i = 0; i < xfer->nframes; i++) + xfer->sizes[i] = sc->sync_pktsz; + + xfer->size = xfer->nframes * sc->sync_pktsz; + +#ifdef UAUDIO_DEBUG + memset(xfer->buf, 0xd0, sc->sync_pktsz * xfer->nframes); #endif - DPRINTFN(5,("%s: transfer xfer=%p\n", __func__, cb->xfer)); - usbd_setup_isoc_xfer(cb->xfer, ch->pipe, cb, cb->sizes, ch->nframes, - USBD_NO_COPY | USBD_SHORT_XFER_OK, uaudio_chan_pintr); + usbd_setup_isoc_xfer(xfer->usb_xfer, s->sync_pipe, sc, + xfer->sizes, xfer->nframes, + USBD_NO_COPY | USBD_SHORT_XFER_OK, + uaudio_psync_intr); + + err = usbd_transfer(xfer->usb_xfer); + if (err != USBD_IN_PROGRESS) + printf("%s: sync play xfer, err = %d\n", DEVNAME(sc), err); + + if (++s->sync_nextxfer == s->nxfers) + s->sync_nextxfer = 0; - (void)usbd_transfer(cb->xfer); +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 3) { + getmicrotime(&tv); + printf("%s: %llu.%06lu: %dB, %d fr\n", __func__, + tv.tv_sec, tv.tv_usec, sc->sync_pktsz, xfer->nframes); + } +#endif } +/* + * Callback called by the USB driver upon completion of play sync transfer. + */ void -uaudio_chan_pintr(struct usbd_xfer *xfer, void *priv, - usbd_status status) +uaudio_psync_intr(struct usbd_xfer *usb_xfer, void *arg, usbd_status status) { - struct chanbuf *cb = priv; - struct chan *ch = cb->chan; - u_int32_t count; +#ifdef UAUDIO_DEBUG + struct timeval tv; +#endif + struct uaudio_softc *sc = arg; + struct uaudio_stream *s = &sc->pstream; + struct uaudio_xfer *xfer; + unsigned char *buf; + unsigned int i; + int32_t val; + + if (status != 0) { + DPRINTF("%s: xfer status = %d\n", __func__, status); + return; + } - /* Return if we are aborting. */ - if (status == USBD_CANCELLED) + xfer = s->sync_xfers + s->sync_nextxfer; + if (xfer->usb_xfer != usb_xfer) { + DPRINTF("%s: wrong xfer\n", __func__); return; + } + + /* XXX: there's only one frame, the loop is not necessary */ - usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); - DPRINTFN(5,("%s: count=%d, transferred=%d\n", - __func__, count, ch->transferred)); + buf = xfer->buf; + for (i = 0; i < xfer->nframes; i++) { + if (xfer->sizes[i] == sc->sync_pktsz) { + val = buf[0] | buf[1] << 8 | buf[2] << 16; + if (sc->sync_pktsz == 4) + val |= xfer->buf[3] << 24; + else + val <<= 2; + val *= UAUDIO_SPF_DIV / (1 << 16); #ifdef UAUDIO_DEBUG - if (count != cb->size) { - printf("%s: count(%d) != size(%d)\n", - __func__, count, cb->size); - } + if (uaudio_debug >= 2) { + getmicrotime(&tv); + printf("%s: %llu.%06lu: spf: %08x\n", + __func__, tv.tv_sec, tv.tv_usec, val); + } #endif + if (val > s->spf_max) + s->spf = s->spf_max; + else if (val < s->spf_min) + s->spf = s->spf_min; + else + s->spf = val; + } + buf += sc->sync_pktsz; + } - /* start next transfer */ - uaudio_chan_ptransfer(ch); + uaudio_psync_xfer(sc); } -/* Called at splusb() */ +/* + * Submit a rec data transfer to the USB driver. + */ void -uaudio_chan_psync_transfer(struct chan *ch) +uaudio_rdata_xfer(struct uaudio_softc *sc) { - struct syncbuf *sb; - int i, size, total = 0; +#ifdef UAUDIO_DEBUG + struct timeval tv; +#endif + struct uaudio_stream *s = &sc->rstream; + struct uaudio_alt *a = sc->params->ralt; + struct uaudio_xfer *xfer; + unsigned int fsize, bpf; + int done; + int err; + + xfer = s->data_xfers + s->data_nextxfer; + bpf = a->bps * a->nch; + xfer->nframes = 0; + done = s->ring_offs; + + while (1) { + /* + * if we crossed the next block boundary, we're done + */ + if ((xfer->nframes & s->nframes_mask) == 0 && + done > s->safe_blksz) { + done: + xfer->size = done - s->ring_offs; + s->ring_offs = done - s->ring_blksz; + break; + } - if (usbd_is_dying(ch->sc->sc_udev)) - return; + /* + * this can't happen, debug only + */ + if (xfer->nframes == s->nframes_max) { + printf("%s: too many frames for rec xfer: " + "done = %d, blksz = %d\n", + DEVNAME(sc), done, s->ring_blksz); + goto done; + } - /* Pick the next sync buffer. */ - sb = &ch->syncbufs[ch->cursyncbuf]; - if (++ch->cursyncbuf >= UAUDIO_NSYNCBUFS) - ch->cursyncbuf = 0; + /* + * estimate next block using s->spf, but allow + * transfers up to maxpkt + */ + s->spf_remain += s->spf; + fsize = s->spf_remain / UAUDIO_SPF_DIV * bpf; + s->spf_remain %= UAUDIO_SPF_DIV; + done += fsize; + xfer->sizes[xfer->nframes] = s->maxpkt; + xfer->nframes++; + } + +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 3) { + getmicrotime(&tv); + printf("%s: %llu.%06lu: " + "%u fr, %d bytes (max %d), offs = %d\n", + __func__, tv.tv_sec, tv.tv_usec, + xfer->nframes, xfer->size, + s->maxpkt * xfer->nframes, s->ring_offs); + } +#endif - size = ch->hi_speed ? 4 : 3; - for (i = 0; i < ch->nsync_frames; i++) { - sb->sizes[i] = size; - sb->offsets[i] = total; - total += size; + /* this can't happen, debug only */ + if (xfer->nframes == 0) { + printf("%s: zero frame rec xfer\n", DEVNAME(sc)); + return; } - sb->size = total; - DPRINTFN(5,("%s: transfer xfer=%p\n", __func__, sb->xfer)); - usbd_setup_isoc_xfer(sb->xfer, ch->sync_pipe, sb, sb->sizes, - ch->nsync_frames, USBD_NO_COPY | USBD_SHORT_XFER_OK, - uaudio_chan_psync_intr); +#ifdef UAUDIO_DEBUG + memset(xfer->buf, 0xd0, s->maxpkt * xfer->nframes); +#endif + usbd_setup_isoc_xfer(xfer->usb_xfer, s->data_pipe, sc, + xfer->sizes, xfer->nframes, USBD_NO_COPY | USBD_SHORT_XFER_OK, + uaudio_rdata_intr); - (void)usbd_transfer(sb->xfer); + err = usbd_transfer(xfer->usb_xfer); + if (err != 0 && err != USBD_IN_PROGRESS) + printf("%s: rec xfer, err = %d\n", DEVNAME(sc), err); + + if (++s->data_nextxfer == s->nxfers) + s->data_nextxfer = 0; } +/* + * Callback called by the USB driver upon completion of rec data transfer. + */ void -uaudio_chan_psync_intr(struct usbd_xfer *xfer, void *priv, - usbd_status status) +uaudio_rdata_intr(struct usbd_xfer *usb_xfer, void *arg, usbd_status status) { - struct syncbuf *sb = priv; - struct chan *ch = sb->chan; - u_int32_t count, tmp; - u_int32_t freq, freq_w, freq_f; - int i, pos, size; +#ifdef UAUDIO_DEBUG + struct timeval tv; +#endif + struct uaudio_softc *sc = arg; + struct uaudio_stream *s = &sc->rstream; + struct uaudio_alt *a = sc->params->ralt; + struct uaudio_xfer *xfer; + unsigned char *buf, *framebuf; + unsigned int count, fsize, fsize_min, nframes, bpf; + unsigned int data_size, null_count; + unsigned int nintr; + + if (status != 0) { + DPRINTF("%s: xfer status = %d\n", __func__, status); + return; + } - /* Return if we are aborting. */ - if (status == USBD_CANCELLED) + xfer = s->data_xfers + s->data_nextxfer; + if (xfer->usb_xfer != usb_xfer) { + DPRINTF("%s: wrong xfer\n", __func__); return; + } - usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); - DPRINTFN(5,("%s: count=%d\n", __func__, count)); + bpf = a->bps * a->nch; + framebuf = xfer->buf; + nframes = 0; + null_count = 0; + data_size = 0; + fsize_min = s->spf_min / UAUDIO_SPF_DIV; + for (nframes = 0; nframes < xfer->nframes; nframes++) { - size = ch->hi_speed ? 4 : 3; - for (i = 0; count > 0 && i < ch->nsync_frames; i++) { - if (sb->sizes[i] != size) - continue; - count -= size; - pos = sb->offsets[i]; - if (ch->hi_speed) { - /* 16.16 (12.13) -> 16.16 (12.16) */ - freq = sb->buffer[pos+3] << 24 | - sb->buffer[pos+2] << 16 | - sb->buffer[pos+1] << 8 | - sb->buffer[pos]; - } else { - /* 10.14 (10.10) -> 16.16 (10.16) */ - freq = sb->buffer[pos+2] << 18 | - sb->buffer[pos+1] << 10 | - sb->buffer[pos] << 2; - } - freq_w = (freq >> 16) & (ch->hi_speed ? 0x0fff : 0x03ff); - freq_f = freq & 0xffff; - DPRINTFN(5,("%s: freq = %d %d/%d\n", __func__, freq_w, freq_f, - ch->frac_denom)); - tmp = freq_w * ch->sample_size; - if (tmp + (freq_f ? ch->sample_size : 0) > - ch->max_bytes_per_frame) { - DPRINTF(("%s: packet size request too large: %d/%d/%d\n", - __func__, tmp, ch->max_bytes_per_frame, ch->maxpktsize)); - } else { - ch->bytes_per_frame = tmp; - ch->fraction = freq_f; + /* + * Device clock may take some time to lock during which + * we'd receive empty or incomplete packets for which we + * need to generate silence. + */ + fsize = xfer->sizes[nframes]; + if (fsize < fsize_min) { + s->spf_remain += s->spf; + fsize = s->spf_remain / UAUDIO_SPF_DIV * bpf; + s->spf_remain %= UAUDIO_SPF_DIV; + memset(framebuf, 0, fsize); + null_count++; } - } + data_size += fsize; - /* start next transfer */ - uaudio_chan_psync_transfer(ch); -} + /* + * fill ring from frame buffer, handling + * boundary conditions + */ + buf = framebuf; + while (fsize > 0) { + count = s->ring_end - s->ring_pos; + if (count > fsize) + count = fsize; + memcpy(s->ring_pos, buf, count); + s->ring_pos += count; + if (s->ring_pos == s->ring_end) + s->ring_pos = s->ring_start; + buf += count; + fsize -= count; + } -/* Called at splusb() */ -void -uaudio_chan_rtransfer(struct chan *ch) -{ - struct chanbuf *cb; - int i, size, total; + framebuf += s->maxpkt; + } - if (usbd_is_dying(ch->sc->sc_udev)) - return; + s->ring_offs += data_size - xfer->size; + s->ring_icnt += data_size; - /* Pick the next channel buffer. */ - cb = &ch->chanbufs[ch->curchanbuf]; - if (++ch->curchanbuf >= UAUDIO_NCHANBUFS) - ch->curchanbuf = 0; + sc->diff_nsamp -= data_size / + (sc->params->ralt->nch * sc->params->ralt->bps); + sc->diff_nframes -= xfer->nframes; - /* Compute the size of each frame in the next transfer. */ - total = 0; - for (i = 0; i < ch->nframes; i++) { - size = ch->bytes_per_frame; - cb->sizes[i] = size; - cb->offsets[i] = total; - total += size; + sc->adjspf_age += xfer->nframes; + if (sc->adjspf_age >= sc->ufps / 8) { + sc->adjspf_age -= sc->ufps / 8; + uaudio_adjspf(sc); } - cb->size = total; #ifdef UAUDIO_DEBUG - if (uaudiodebug > 8) { - DPRINTF(("%s: buffer=%p, residue=0.%03d\n", - __func__, cb->buffer, ch->residue)); - for (i = 0; i < ch->nframes; i++) { - DPRINTF((" [%d] length %d\n", i, cb->sizes[i])); - } + if (uaudio_debug >= 2) { + getmicrotime(&tv); + printf("%s: %llu.%06lu: %u: " + "%u bytes of %u, offs -> %d\n", + __func__, tv.tv_sec, tv.tv_usec, + s->data_nextxfer, data_size, xfer->size, s->ring_offs); + } + if (null_count > 0) { + printf("%s: %u null frames out of %u: incomplete record xfer\n", + DEVNAME(sc), null_count, xfer->nframes); } #endif - - DPRINTFN(5,("%s: transfer xfer=%p\n", __func__, cb->xfer)); - usbd_setup_isoc_xfer(cb->xfer, ch->pipe, cb, cb->sizes, ch->nframes, - USBD_NO_COPY | USBD_SHORT_XFER_OK, uaudio_chan_rintr); - - (void)usbd_transfer(cb->xfer); + uaudio_rdata_xfer(sc); + + nintr = 0; + mtx_enter(&audio_lock); + while (s->ring_icnt >= s->ring_blksz) { + s->intr(s->arg); + s->ring_icnt -= s->ring_blksz; + nintr++; + } + mtx_leave(&audio_lock); + if (nintr != 1) + printf("%s: %u: bad rec intr count\n", DEVNAME(sc), nintr); } +/* + * Start simultaneously playback and recording, unless trigger_input() + * and trigger_output() were not both called yet. + */ void -uaudio_chan_rintr(struct usbd_xfer *xfer, void *priv, - usbd_status status) +uaudio_trigger(struct uaudio_softc *sc) { - struct chanbuf *cb = priv; - struct chan *ch = cb->chan; - u_int16_t pos; - u_int32_t count; - int i, n, frsize; + int i, s; - /* Return if we are aborting. */ - if (status == USBD_CANCELLED) + if (sc->mode != sc->trigger_mode) return; - usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); - DPRINTFN(5,("%s: count=%d, transferred=%d\n", - __func__, count, ch->transferred)); + DPRINTF("%s: preparing\n", __func__); + if (sc->mode & AUMODE_PLAY) { + for (i = 0; i < sc->pstream.nxfers; i++) + uaudio_pdata_calcsizes(sc, sc->pstream.data_xfers + i); - /* count < cb->size is normal for asynchronous source */ -#ifdef DIAGNOSTIC - if (count > cb->size) { - printf("%s: count(%d) > size(%d)\n", - __func__, count, cb->size); + uaudio_pdata_copy(sc); } -#endif - /* - * Transfer data from channel buffer to upper layer buffer, taking - * care of wrapping the upper layer buffer. - */ - for (i = 0; i < ch->nframes; i++) { - frsize = cb->sizes[i]; - pos = cb->offsets[i]; - while (frsize > 0) { - n = min(frsize, ch->end - ch->cur); - n = min(n, ch->blksize - ch->transferred); - memcpy(ch->cur, cb->buffer + pos, n); - frsize -= n; - pos += n; - ch->cur += n; - if (ch->cur >= ch->end) - ch->cur = ch->start; - - ch->transferred += n; - /* Call back to upper layer */ - if (ch->transferred >= ch->blksize) { - DPRINTFN(5,("%s: call %p(%p)\n", - __func__, ch->intr, ch->arg)); - mtx_enter(&audio_lock); - ch->intr(ch->arg); - mtx_leave(&audio_lock); - ch->transferred -= ch->blksize; - } - if (count < n) - printf("%s: count < n\n", __func__); - else - count -= n; + sc->diff_nsamp = 0; + sc->diff_nframes = 0; + sc->adjspf_age = 0; + + DPRINTF("%s: starting\n", __func__); + s = splusb(); + for (i = 0; i < UAUDIO_NXFERS_MAX; i++) { + if ((sc->mode & AUMODE_PLAY) && i < sc->pstream.nxfers) { + if (sc->pstream.sync_pipe) + uaudio_psync_xfer(sc); + uaudio_pdata_xfer(sc); } + if ((sc->mode & AUMODE_RECORD) && i < sc->rstream.nxfers) + uaudio_rdata_xfer(sc); } - if (count != 0) { - printf("%s: transfer count - frame total = %d\n", - __func__, count); - } - - /* start next transfer */ - uaudio_chan_rtransfer(ch); + splx(s); } void -uaudio_chan_init(struct chan *ch, int mode, int altidx, - const struct audio_params *param) -{ - struct as_info *ai = &ch->sc->sc_alts[altidx]; - int samples_per_frame, ival, use_maxpkt = 0; - - if (ai->attributes & UA_SED_MAXPACKETSONLY) { - DPRINTF(("%s: alt %d needs maxpktsize packets\n", - __func__, altidx)); - use_maxpkt = 1; - } - else if (mode == AUMODE_RECORD) { - DPRINTF(("%s: using maxpktsize packets for record channel\n", - __func__)); - use_maxpkt = 1; - } - - ch->altidx = altidx; - ch->maxpktsize = UGETW(ai->edesc->wMaxPacketSize); - ch->sample_rate = param->sample_rate; - ch->sample_size = param->channels * param->bps; - ch->usb_fps = USB_FRAMES_PER_SECOND; - ch->hi_speed = ch->sc->sc_udev->speed == USB_SPEED_HIGH; - if (ch->hi_speed) { - ch->usb_fps *= 8; - /* - * Polling interval is considered a frame, as opposed to - * micro-frame being a frame. - */ - ival = ch->sc->sc_alts[altidx].edesc->bInterval; - if (ival > 0 && ival <= 4) - ch->usb_fps >>= (ival - 1); - DPRINTF(("%s: detected USB high-speed with ival %d\n", - __func__, ival)); +uaudio_print(struct uaudio_softc *sc) +{ + struct uaudio_unit *u; + struct uaudio_mixent *m; + struct uaudio_params *p; + int pchan = 0, rchan = 0, async = 0; + int nctl = 0; + + for (u = sc->unit_list; u != NULL; u = u->unit_next) { + for (m = u->mixent_list; m != NULL; m = m->next) + nctl++; } - /* - * Use UAUDIO_MIN_FRAMES here, so uaudio_round_blocksize() can - * make sure the blocksize duration will be > 1 USB frame. - */ - samples_per_frame = ch->sample_rate / ch->usb_fps; - if (!use_maxpkt) { - ch->fraction = ch->sample_rate % ch->usb_fps; - if (samples_per_frame * ch->sample_size > ch->maxpktsize) { - DPRINTF(("%s: packet size %d too big, max %d\n", - __func__, ch->bytes_per_frame, ch->maxpktsize)); - samples_per_frame = ch->maxpktsize / ch->sample_size; - } - ch->bytes_per_frame = samples_per_frame * ch->sample_size; - ch->nframes = UAUDIO_MIN_FRAMES; - } else { - ch->fraction = 0; - ch->bytes_per_frame = ch->maxpktsize; - ch->nframes = UAUDIO_MIN_FRAMES * samples_per_frame * - ch->sample_size / ch->maxpktsize; - } - if (ch->nframes > UAUDIO_MAX_FRAMES) - ch->nframes = UAUDIO_MAX_FRAMES; - else if (ch->nframes < 1) - ch->nframes = 1; - - ch->max_bytes_per_frame = ch->bytes_per_frame; - if (!use_maxpkt) - ch->max_bytes_per_frame += ch->sample_size; - if (ch->max_bytes_per_frame > ch->maxpktsize) - ch->max_bytes_per_frame = ch->maxpktsize; - - ch->residue = 0; - ch->frac_denom = ch->usb_fps; - if (ai->edesc1 != NULL) { - /* - * The lower 16-bits of the sync request represent - * fractional samples. Scale up the fraction here once - * so all fractions are using the same denominator. - */ - ch->frac_denom = 1 << 16; - ch->fraction = (ch->fraction * ch->frac_denom) / ch->usb_fps; + for (p = sc->params_list; p != NULL; p = p->next) { + if (p->palt && p->palt->nch > pchan) + pchan = p->palt->nch; + if (p->ralt && p->ralt->nch > rchan) + rchan = p->ralt->nch; + if (p->palt && p->palt->sync_addr) + async = 1; + if (p->ralt && p->ralt->sync_addr) + async = 1; + } - /* - * Have to set nsync_frames somewhere. We can request - * a lot of sync data; the device will reply when it's - * ready, with empty frames meaning to keep using the - * current rate. - */ - ch->nsync_frames = UAUDIO_MAX_FRAMES; + printf("%s: class v%d, %s, %s, channels: %d play, %d rec, %d ctls\n", + DEVNAME(sc), + sc->version >> 8, + sc->ufps == 1000 ? "full-speed" : "high-speed", + async ? "async" : "sync", + pchan, rchan, nctl); +} + +int +uaudio_match(struct device *parent, void *match, void *aux) +{ + struct usb_attach_arg *arg = aux; + struct usb_interface_descriptor *idesc; + + if (arg->iface == NULL || arg->device == NULL) + return UMATCH_NONE; + + idesc = usbd_get_interface_descriptor(arg->iface); + if (idesc == NULL) { + DPRINTF("%s: couldn't get idesc\n", __func__); + return UMATCH_NONE; } - DPRINTF(("%s: residual sample fraction: %d/%d\n", __func__, - ch->fraction, ch->frac_denom)); + + if (idesc->bInterfaceClass != UICLASS_AUDIO || + idesc->bInterfaceSubClass != UISUBCLASS_AUDIOSTREAM) + return UMATCH_NONE; + + return UMATCH_VENDOR_PRODUCT_CONF_IFACE; } void -uaudio_chan_set_param(struct chan *ch, u_char *start, u_char *end, int blksize) +uaudio_attach(struct device *parent, struct device *self, void *aux) { - ch->start = start; - ch->end = end; - ch->cur = start; - ch->transferred = 0; - ch->curchanbuf = 0; - ch->blksize = blksize; + struct uaudio_softc *sc = (struct uaudio_softc *)self; + struct usb_attach_arg *arg = aux; + struct usb_config_descriptor *cdesc; + struct uaudio_blob desc; /* - * Recompute nframes based on blksize, but make sure nframes - * is not longer in time duration than blksize. + * this device has audio AC or AS or MS interface, get the + * full config descriptor and attach audio devices */ - ch->nframes = ch->blksize * ch->usb_fps / - (ch->bytes_per_frame * ch->usb_fps + - ch->sample_size * ch->fraction); - if (ch->nframes > UAUDIO_MAX_FRAMES) - ch->nframes = UAUDIO_MAX_FRAMES; - else if (ch->nframes < 1) - ch->nframes = 1; - ch->reqms = ch->bytes_per_frame / ch->sample_size * - ch->nframes * 1000 / ch->sample_rate; + cdesc = usbd_get_config_descriptor(arg->device); + if (cdesc == NULL) + return; + + desc.rptr = (unsigned char *)cdesc; + desc.wptr = desc.rptr + UGETW(cdesc->wTotalLength); + + sc->udev = arg->device; + sc->unit_list = NULL; + sc->names = NULL; + sc->alts = NULL; + sc->params_list = NULL; + sc->clock = NULL; + sc->params = NULL; + sc->rate = 0; + sc->mode = 0; + sc->trigger_mode = 0; + sc->copy_todo = 0; + + /* + * Ideally the USB host controller should expose the number of + * frames we're allowed to schedule, but there's no such + * interface. The uhci(4) driver can buffer up to 128 frames + * (or it crashes), ehci(4) starts recording null frames if we + * exceed 256 (micro-)frames, ohci(4) works with at most 50 + * frames. + */ + switch (sc->udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + sc->ufps = 1000; + sc->sync_pktsz = 3; + sc->host_nframes = 50; + break; + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + sc->ufps = 8000; + sc->sync_pktsz = 4; + sc->host_nframes = 240; + break; + default: + printf("%s: unsupported bus speed\n", DEVNAME(sc)); + return; + } + + if (!uaudio_process_conf(sc, &desc)) + return; + +#ifdef UAUDIO_DEBUG + if (uaudio_debug) + uaudio_conf_print(sc); +#endif + /* print a nice uaudio attach line */ + uaudio_print(sc); - DPRINTF(("%s: alt=%d blk=%d maxpkt=%u bpf=%u rate=%u nframes=%u reqms=%u\n", - __func__, ch->altidx, ch->blksize, ch->maxpktsize, - ch->bytes_per_frame, ch->sample_rate, ch->nframes, ch->reqms)); + audio_attach_mi(&uaudio_hw_if, sc, &sc->dev); } int -uaudio_match_alt_rate(void *addr, int alt, int rate) +uaudio_detach(struct device *self, int flags) { - struct uaudio_softc *sc = addr; - const struct usb_audio_streaming_type1_descriptor *a1d; - int i, j, r; + struct uaudio_softc *sc = (struct uaudio_softc *)self; + struct uaudio_unit *unit; + struct uaudio_params *params; + struct uaudio_alt *alt; + struct uaudio_name *name; + struct uaudio_mixent *mixent; + int rv; + + rv = config_detach_children(self, flags); + + while ((alt = sc->alts) != NULL) { + sc->alts = alt->next; + free(alt, M_DEVBUF, sizeof(struct uaudio_alt)); + } - a1d = sc->sc_alts[alt].asf1desc; - if (a1d->bSamFreqType == UA_SAMP_CONTNUOUS) { - if ((UA_SAMP_LO(a1d) <= rate) && - (UA_SAMP_HI(a1d) >= rate)) { - return rate; - } else { - if (UA_SAMP_LO(a1d) > rate) - return UA_SAMP_LO(a1d); - else - return UA_SAMP_HI(a1d); - } - } else { - for (i = 0; i < 100; i++) { - for (j = 0; j < a1d->bSamFreqType; j++) { - r = UA_GETSAMP(a1d, j); - if ((r - (500 * i) <= rate) && - (r + (500 * i) >= rate)) - return r; - } + while ((params = sc->params_list) != NULL) { + sc->params_list = params->next; + free(params, M_DEVBUF, sizeof(struct uaudio_params)); + } + + while ((unit = sc->unit_list) != NULL) { + sc->unit_list = unit->unit_next; + while ((mixent = unit->mixent_list) != NULL) { + unit->mixent_list = mixent->next; + uaudio_ranges_clear(&mixent->ranges); + free(mixent, M_DEVBUF, sizeof(struct uaudio_mixent)); } - /* assumes rates are listed in order from lowest to highest */ - if (rate < UA_GETSAMP(a1d, 0)) - j = 0; - else - j = a1d->bSamFreqType - 1; - return UA_GETSAMP(a1d, j); + uaudio_ranges_clear(&unit->rates); + free(unit, M_DEVBUF, sizeof(struct uaudio_unit)); + } + + while ((name = sc->names)) { + sc->names = name->next; + free(name, M_DEVBUF, sizeof(struct uaudio_name)); } - DPRINTF(("%s: could not match rate\n", __func__)); - return rate; + + return rv; } int -uaudio_match_alt(void *addr, struct audio_params *p, int mode) -{ - struct uaudio_softc *sc = addr; - const struct usb_audio_streaming_type1_descriptor *a1d; - int i, j, dir, rate; - int alts_eh, alts_ch, ualt; - - DPRINTF(("%s: mode=%s rate=%ld ch=%d pre=%d bps=%d enc=%d\n", - __func__, mode == AUMODE_RECORD ? "rec" : "play", p->sample_rate, - p->channels, p->precision, p->bps, p->encoding)); - - alts_eh = 0; - for (i = 0; i < sc->sc_nalts; i++) { - dir = UE_GET_DIR(sc->sc_alts[i].edesc->bEndpointAddress); - if ((mode == AUMODE_RECORD && dir != UE_DIR_IN) || - (mode == AUMODE_PLAY && dir == UE_DIR_IN)) - continue; - DPRINTFN(6,("%s: matched %s alt %d for direction\n", __func__, - mode == AUMODE_RECORD ? "rec" : "play", i)); - if (sc->sc_alts[i].encoding != p->encoding) - continue; - a1d = sc->sc_alts[i].asf1desc; - if (a1d->bBitResolution != p->precision) - continue; - alts_eh |= 1 << i; - DPRINTFN(6,("%s: matched %s alt %d for enc/pre\n", __func__, - mode == AUMODE_RECORD ? "rec" : "play", i)); - } - if (alts_eh == 0) { - DPRINTF(("%s: could not match dir/enc/prec\n", __func__)); - return -1; - } +uaudio_open(void *self, int flags) +{ + struct uaudio_softc *sc = self; + struct uaudio_params *p; - alts_ch = 0; - for (i = 0; i < 3; i++) { - for (j = 0; j < sc->sc_nalts; j++) { - if (!(alts_eh & (1 << j))) - continue; - a1d = sc->sc_alts[j].asf1desc; - if (a1d->bNrChannels == p->channels) { - alts_ch |= 1 << j; - DPRINTFN(6,("%s: matched alt %d for channels\n", - __func__, j)); - } - } - if (alts_ch) - break; - if (p->channels == 2) - p->channels = 1; - else - p->channels = 2; - } - if (!alts_ch) { - /* just use the first alt that matched the encoding */ - for (i = 0; i < sc->sc_nalts; i++) - if (alts_eh & (1 << i)) + if (usbd_is_dying(sc->udev)) + return EIO; + + flags &= (FREAD | FWRITE); + + for (p = sc->params_list; p != NULL; p = p->next) { + switch (flags) { + case FWRITE: + if (!p->palt) break; - alts_ch = 1 << i; - a1d = sc->sc_alts[i].asf1desc; - p->channels = a1d->bNrChannels; - } - - ualt = -1; - for (i = 0; i < sc->sc_nalts; i++) { - if (alts_ch & (1 << i)) { - rate = uaudio_match_alt_rate(sc, i, p->sample_rate); - if (rate - 50 <= p->sample_rate && - rate + 50 >= p->sample_rate) { - DPRINTFN(6,("%s: alt %d matched rate %ld with %d\n", - __func__, i, p->sample_rate, rate)); - p->sample_rate = rate; + sc->mode = AUMODE_PLAY; + return 0; + case FREAD: + if (!p->ralt) break; - } - } - } - if (i < sc->sc_nalts) { - ualt = i; - } else { - for (i = 0; i < sc->sc_nalts; i++) { - if (alts_ch & (1 << i)) { - ualt = i; - p->sample_rate = uaudio_match_alt_rate(sc, - i, p->sample_rate); + sc->mode = AUMODE_RECORD; + return 0; + case FREAD | FWRITE: + if (!(p->ralt && p->palt)) break; - } + sc->mode = AUMODE_RECORD | AUMODE_PLAY; + return 0; } } - return ualt; + return ENXIO; } -int -uaudio_set_params(void *addr, int setmode, int usemode, - struct audio_params *play, struct audio_params *rec) +void +uaudio_close(void *self) { - struct uaudio_softc *sc = addr; - int flags = sc->sc_altflags; - int i; - int paltidx = -1, raltidx = -1; - struct audio_params *p; - int mode; + struct uaudio_softc *sc = self; - if (usbd_is_dying(sc->sc_udev)) - return (EIO); + sc->mode = 0; +} - if (((usemode & AUMODE_PLAY) && sc->sc_playchan.pipe != NULL) || - ((usemode & AUMODE_RECORD) && sc->sc_recchan.pipe != NULL)) - return (EBUSY); +int +uaudio_set_params(void *self, int setmode, int usemode, + struct audio_params *ap, struct audio_params *ar) +{ + struct uaudio_softc *sc = (struct uaudio_softc *)self; + struct uaudio_params *p, *best_mode, *best_rate, *best_nch; + int rate, rateindex; - if ((usemode & AUMODE_PLAY) && sc->sc_playchan.altidx != -1) - sc->sc_alts[sc->sc_playchan.altidx].sc_busy = 0; - if ((usemode & AUMODE_RECORD) && sc->sc_recchan.altidx != -1) - sc->sc_alts[sc->sc_recchan.altidx].sc_busy = 0; +#ifdef DIAGNOSTIC + if (setmode != usemode || setmode != sc->mode) { + printf("%s: bad call to uaudio_set_params()\n", DEVNAME(sc)); + return EINVAL; + } + if (sc->mode == 0) { + printf("%s: uaudio_set_params(): not open\n", DEVNAME(sc)); + return EINVAL; + } +#endif + /* + * audio(4) layer requests equal play and record rates + */ + rate = (sc->mode & AUMODE_PLAY) ? ap->sample_rate : ar->sample_rate; + rateindex = uaudio_rates_indexof(~0, rate); - /* Some uaudio devices are unidirectional. Don't try to find a - matching mode for the unsupported direction. */ - setmode &= sc->sc_mode; + DPRINTF("%s: rate %d -> %d (index %d)\n", __func__, + rate, uaudio_rates[rateindex], rateindex); - for (mode = AUMODE_RECORD; mode != -1; - mode = mode == AUMODE_RECORD ? AUMODE_PLAY : -1) { - if ((setmode & mode) == 0) - continue; + best_mode = best_rate = best_nch = NULL; - p = (mode == AUMODE_PLAY) ? play : rec; + for (p = sc->params_list; p != NULL; p = p->next) { - switch (p->precision) { - case 24: - if (!(flags & HAS_24)) { - if (flags & HAS_16) - p->precision = 16; - else - p->precision = 8; - } - break; - case 16: - if (!(flags & HAS_16)) { - if (flags & HAS_24) - p->precision = 24; - else - p->precision = 8; - } - break; - case 8: - if (!(flags & HAS_8) && !(flags & HAS_8U)) { - if (flags & HAS_16) - p->precision = 16; - else - p->precision = 24; - } - break; + /* test if params match the requested mode */ + if (sc->mode & AUMODE_PLAY) { + if (p->palt == NULL) + continue; } + if (sc->mode & AUMODE_RECORD) { + if (p->ralt == NULL) + continue; + } + if (best_mode == NULL) + best_mode = p; - i = uaudio_match_alt(sc, p, mode); - if (i < 0) { - DPRINTF(("%s: uaudio_match_alt failed for %s\n", - __func__, mode == AUMODE_RECORD ? "rec" : "play")); + /* test if params match the requested rate */ + if ((uaudio_getrates(sc, p) & (1 << rateindex)) == 0) continue; + if (best_rate == NULL) + best_rate = p; + + /* test if params match the requested channel counts */ + if (sc->mode & AUMODE_PLAY) { + if (p->palt->nch != ap->channels) + continue; + } + if (sc->mode & AUMODE_RECORD) { + if (p->ralt->nch != ar->channels) + continue; } + if (best_nch == NULL) + best_nch = p; - p->bps = sc->sc_alts[i].asf1desc->bSubFrameSize; - p->msb = 1; + /* test if params match the requested precision */ + if (sc->mode & AUMODE_PLAY) { + if (p->palt->bits != ap->precision) + continue; + } + if (sc->mode & AUMODE_RECORD) { + if (p->ralt->bits != ar->precision) + continue; + } - if (mode == AUMODE_PLAY) - paltidx = i; + /* everything matched, we're done */ + break; + } + + if (p == NULL) { + if (best_nch) + p = best_nch; + else if (best_rate) + p = best_rate; + else if (best_mode) + p = best_mode; else - raltidx = i; + return ENOTTY; } - if (setmode & AUMODE_PLAY) { - if (paltidx == -1) { - DPRINTF(("%s: did not find alt for playback\n", - __func__)); - return (EINVAL); - } - /* XXX abort transfer if currently happening? */ - uaudio_chan_init(&sc->sc_playchan, AUMODE_PLAY, paltidx, play); + /* + * Recalculate rate index, because the choosen parameters + * may not support the requested one + */ + rateindex = uaudio_rates_indexof(uaudio_getrates(sc, p), rate); + if (rateindex < 0) + return ENOTTY; + + sc->params = p; + sc->rate = uaudio_rates[rateindex]; + + DPRINTF("%s: rate = %u\n", __func__, sc->rate); + + if (sc->mode & AUMODE_PLAY) { + ap->sample_rate = sc->rate; + ap->precision = p->palt->bits; + ap->encoding = AUDIO_ENCODING_SLINEAR_LE; + ap->bps = p->palt->bps; + ap->msb = 1; + ap->channels = p->palt->nch; } - if (setmode & AUMODE_RECORD) { - if (raltidx == -1) { - DPRINTF(("%s: did not find alt for recording\n", - __func__)); - return (EINVAL); - } - /* XXX abort transfer if currently happening? */ - uaudio_chan_init(&sc->sc_recchan, AUMODE_RECORD, raltidx, rec); + if (sc->mode & AUMODE_RECORD) { + ar->sample_rate = sc->rate; + ar->precision = p->ralt->bits; + ar->encoding = AUDIO_ENCODING_SLINEAR_LE; + ar->bps = p->ralt->bps; + ar->msb = 1; + ar->channels = p->ralt->nch; } - if ((usemode & AUMODE_PLAY) && sc->sc_playchan.altidx != -1) - sc->sc_alts[sc->sc_playchan.altidx].sc_busy = 1; - if ((usemode & AUMODE_RECORD) && sc->sc_recchan.altidx != -1) - sc->sc_alts[sc->sc_recchan.altidx].sc_busy = 1; + return 0; +} + +int +uaudio_round_blocksize(void *self, int blksz) +{ + struct uaudio_softc *sc = self; + struct uaudio_alt *a; + unsigned int rbpf, pbpf; + unsigned int blksz_max; + + /* + * XXX: We don't know if we're called for the play or record + * direction, so we can't calculate maximum blksz. This would + * require a change in the audio(9) interface. Meanwhile, we + * use the direction with the greatest sample size; it gives + * the correct result: indeed, if we return: + * + * blksz_max = max(pbpf, rbpf) * nsamp_max + * + * in turn the audio(4) layer will use: + * + * min(blksz_max / pbpf, blksz_max / rbpf) + * + * which is exactly nsamp_max. + */ + + if (sc->mode & AUMODE_PLAY) { + a = sc->params->palt; + pbpf = a->bps * a->nch; + } else + pbpf = 1; + + if (sc->mode & AUMODE_RECORD) { + a = sc->params->ralt; + rbpf = a->bps * a->nch; + } else + rbpf = 1; + + /* + * Limit the block size to (slightly more than): + * + * sc->host_nframes / UAUDIO_NXFERS_MIN + * + * (micro-)frames of audio. Transfers are slightly larger than + * the audio block size (few bytes to make the "safe" block + * size plus one extra millisecond). We reserve an extra 15% + * for that. + */ + blksz_max = (pbpf > rbpf ? pbpf : rbpf) * + sc->rate * (sc->host_nframes / UAUDIO_NXFERS_MIN) / sc->ufps * + 85 / 100; + + return blksz < blksz_max ? blksz : blksz_max; +} - DPRINTF(("%s: use altidx=p%d/r%d, altno=p%d/r%d\n", __func__, - sc->sc_playchan.altidx, sc->sc_recchan.altidx, - (sc->sc_playchan.altidx >= 0) - ?sc->sc_alts[sc->sc_playchan.altidx].idesc->bAlternateSetting - : -1, - (sc->sc_recchan.altidx >= 0) - ? sc->sc_alts[sc->sc_recchan.altidx].idesc->bAlternateSetting - : -1)); +int +uaudio_trigger_output(void *self, void *start, void *end, int blksz, + void (*intr)(void *), void *arg, struct audio_params *param) +{ + struct uaudio_softc *sc = self; + int err; - return (0); + err = uaudio_stream_open(sc, + AUMODE_PLAY, start, end, blksz, intr, arg); + if (err) + return err; + + sc->trigger_mode |= AUMODE_PLAY; + uaudio_trigger(sc); + return 0; } -usbd_status -uaudio_set_speed(struct uaudio_softc *sc, int endpt, u_int speed) +int +uaudio_trigger_input(void *self, void *start, void *end, int blksz, + void (*intr)(void *), void *arg, struct audio_params *param) { - usb_device_request_t req; - u_int8_t data[3]; + struct uaudio_softc *sc = self; + int err; - DPRINTFN(5,("%s: endpt=%d speed=%u\n", __func__, endpt, speed)); - req.bmRequestType = UT_WRITE_CLASS_ENDPOINT; - req.bRequest = SET_CUR; - USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0); - USETW(req.wIndex, endpt); - USETW(req.wLength, 3); - data[0] = speed; - data[1] = speed >> 8; - data[2] = speed >> 16; + err = uaudio_stream_open(sc, + AUMODE_RECORD, start, end, blksz, intr, arg); + if (err) + return err; - return (usbd_do_request(sc->sc_udev, &req, data)); + sc->trigger_mode |= AUMODE_RECORD; + uaudio_trigger(sc); + return 0; } void -uaudio_set_speed_emu0202(struct chan *ch) +uaudio_copy_output(void *self, size_t todo) { - usb_device_request_t req; - int rates[6] = { 44100, 48000, 88200, 96000, 176400, 192000 }; - int i; - u_int8_t data[1]; + struct uaudio_softc *sc = (struct uaudio_softc *)self; + int s; - for (i = 0; i < 6; i++) - if (rates[i] >= ch->sample_rate) - break; - if (i >= 6) { - DPRINTF(("%s: unhandled rate %d\n", __func__, ch->sample_rate)); - i = 0; + s = splusb(); + sc->copy_todo += todo; + +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 3) { + printf("%s: copy_todo -> %zd (+%zd)\n", __func__, + sc->copy_todo, todo); + } +#endif + + if (sc->mode == sc->trigger_mode) + uaudio_pdata_copy(sc); + splx(s); +} + +void +uaudio_underrun(void *self) +{ + struct uaudio_softc *sc = (struct uaudio_softc *)self; + struct uaudio_stream *s = &sc->pstream; + size_t size; + + /* skip one block in audio fifo */ + s->ring_pos += s->ring_blksz; + if (s->ring_pos >= s->ring_end) + s->ring_pos -= s->ring_end - s->ring_start; + + /* get current transfer size */ + size = s->data_xfers[s->ubuf_xfer].size; + + /* skip one block in usb fifo */ + s->ubuf_pos += s->ring_blksz; + if (s->ubuf_pos >= size) { + s->ubuf_pos -= size; + if (++s->ubuf_xfer == s->nxfers) + s->ubuf_xfer = 0; } - req.bmRequestType = UT_WRITE_CLASS_INTERFACE; - req.bRequest = SET_CUR; - USETW2(req.wValue, 0x03, 0); - USETW2(req.wIndex, 12, ch->sc->sc_ac_iface); - USETW(req.wLength, 1); - data[0] = i; +#ifdef UAUDIO_DEBUG + if (uaudio_debug >= 2) { + printf("%s: ring_pos -> %zd, ubuf_pos -> %u:%u\n", __func__, + s->ring_pos - s->ring_start, s->ubuf_xfer, s->ubuf_pos); + } +#endif +} + +int +uaudio_halt_output(void *self) +{ + struct uaudio_softc *sc = (struct uaudio_softc *)self; + + uaudio_stream_close(sc, AUMODE_PLAY); + sc->trigger_mode &= ~AUMODE_PLAY; + sc->copy_todo = 0; + return 0; +} + +int +uaudio_halt_input(void *self) +{ + struct uaudio_softc *sc = (struct uaudio_softc *)self; + + uaudio_stream_close(sc, AUMODE_RECORD); + sc->trigger_mode &= ~AUMODE_RECORD; + return 0; +} - usbd_do_request(ch->sc->sc_udev, &req, data); +int +uaudio_get_props(void *self) +{ + return AUDIO_PROP_FULLDUPLEX; +} + +int +uaudio_get_port(void *arg, struct mixer_ctrl *ctl) +{ + struct uaudio_softc *sc = arg; + struct uaudio_unit *u; + struct uaudio_mixent *m; + unsigned char req_buf[4]; + struct uaudio_blob p; + int i, nch, val; + + if (!uaudio_mixer_byindex(sc, ctl->dev, &u, &m)) + return ENOENT; + + switch (m->type) { + case UAUDIO_MIX_SW: + p.rptr = p.wptr = req_buf; + if (!uaudio_req(sc, + UT_READ_CLASS_INTERFACE, + UAUDIO_V1_REQ_GET_CUR, + m->req_sel, + m->chan < 0 ? 0 : m->chan, + sc->ctl_ifnum, + u->id, + req_buf, + 1)) + return EIO; + p.wptr++; + if (!uaudio_getnum(&p, 1, &val)) + return EIO; + ctl->un.ord = !!val; + break; + case UAUDIO_MIX_NUM: + nch = uaudio_mixer_nchan(m, NULL); + ctl->un.value.num_channels = nch; + for (i = 0; i < nch; i++) { + p.rptr = p.wptr = req_buf; + if (!uaudio_req(sc, + UT_READ_CLASS_INTERFACE, + UAUDIO_V1_REQ_GET_CUR, + m->req_sel, + m->chan < 0 ? 0 : i + 1, + sc->ctl_ifnum, + u->id, + req_buf, + 2)) + return EIO; + p.wptr += 2; + if (!uaudio_getnum(&p, 2, &val)) + return EIO; + ctl->un.value.level[i] = + uaudio_ranges_decode(&m->ranges, + uaudio_sign_expand(val, 2)); + m = m->next; + } + break; + case UAUDIO_MIX_ENUM: + /* XXX: not used yet */ + break; + } + return 0; +} + +int +uaudio_set_port(void *arg, struct mixer_ctrl *ctl) +{ + struct uaudio_softc *sc = arg; + struct uaudio_unit *u; + struct uaudio_mixent *m; + unsigned char req_buf[4]; + unsigned int val; + int i, nch; + + if (!uaudio_mixer_byindex(sc, ctl->dev, &u, &m)) + return ENOENT; + + switch (m->type) { + case UAUDIO_MIX_SW: + if (ctl->un.ord < 0 || ctl->un.ord > 1) + return EINVAL; + req_buf[0] = ctl->un.ord; + if (!uaudio_req(sc, + UT_WRITE_CLASS_INTERFACE, + UAUDIO_V1_REQ_SET_CUR, + m->req_sel, + m->chan < 0 ? 0 : m->chan, + sc->ctl_ifnum, + u->id, + req_buf, + 1)) + return EIO; + break; + case UAUDIO_MIX_NUM: + nch = uaudio_mixer_nchan(m, NULL); + ctl->un.value.num_channels = nch; + for (i = 0; i < nch; i++) { + val = uaudio_ranges_encode(&m->ranges, + ctl->un.value.level[i]); + DPRINTF("%s: ch %d, ctl %d, num val %d\n", __func__, + i, ctl->un.value.level[i], val); + req_buf[0] = val; + req_buf[1] = val >> 8; + if (!uaudio_req(sc, + UT_WRITE_CLASS_INTERFACE, + UAUDIO_V1_REQ_SET_CUR, + m->req_sel, + m->chan < 0 ? 0 : i + 1, + sc->ctl_ifnum, + u->id, + req_buf, + 2)) + return EIO; + m = m->next; + } + break; + case UAUDIO_MIX_ENUM: + /* XXX: not used yet */ + break; + } + return 0; +} + +int +uaudio_query_devinfo(void *arg, struct mixer_devinfo *devinfo) +{ + struct uaudio_softc *sc = arg; + struct uaudio_unit *u; + struct uaudio_mixent *m; + + devinfo->next = -1; + devinfo->prev = -1; + switch (devinfo->index) { + case UAUDIO_CLASS_REC: + strlcpy(devinfo->label.name, AudioCrecord, MAX_AUDIO_DEV_LEN); + devinfo->type = AUDIO_MIXER_CLASS; + devinfo->mixer_class = -1; + return 0; + case UAUDIO_CLASS_IN: + strlcpy(devinfo->label.name, AudioCinputs, MAX_AUDIO_DEV_LEN); + devinfo->type = AUDIO_MIXER_CLASS; + devinfo->mixer_class = -1; + return 0; + case UAUDIO_CLASS_OUT: + strlcpy(devinfo->label.name, AudioCoutputs, MAX_AUDIO_DEV_LEN); + devinfo->type = AUDIO_MIXER_CLASS; + devinfo->mixer_class = -1; + return 0; + } + + /* + * find the unit & mixent structure for the given index + */ + if (!uaudio_mixer_byindex(sc, devinfo->index, &u, &m)) + return ENOENT; + + if (strcmp(m->fname, "level") == 0) { + /* + * mixer(4) interface doesn't give a names to level + * controls + */ + strlcpy(devinfo->label.name, u->name, MAX_AUDIO_DEV_LEN); + } else { + snprintf(devinfo->label.name, + MAX_AUDIO_DEV_LEN, "%s_%s", u->name, m->fname); + } + + devinfo->mixer_class = u->mixer_class; + switch (m->type) { + case UAUDIO_MIX_SW: + devinfo->type = AUDIO_MIXER_ENUM; + devinfo->un.e.num_mem = 2; + devinfo->un.e.member[0].ord = 0; + strlcpy(devinfo->un.e.member[0].label.name, "off", + MAX_AUDIO_DEV_LEN); + devinfo->un.e.member[1].ord = 1; + strlcpy(devinfo->un.e.member[1].label.name, "on", + MAX_AUDIO_DEV_LEN); + break; + case UAUDIO_MIX_NUM: + devinfo->type = AUDIO_MIXER_VALUE; + devinfo->un.v.num_channels = uaudio_mixer_nchan(m, NULL); + devinfo->un.v.delta = 1; + break; + case UAUDIO_MIX_ENUM: + /* XXX: not used yet */ + devinfo->type = AUDIO_MIXER_ENUM; + devinfo->un.e.num_mem = 0; + break; + } + return 0; } |