diff --git a/share/man/man4/pcm.4 b/share/man/man4/pcm.4 index 769f562fe91e..e25ffe264ae8 100644 --- a/share/man/man4/pcm.4 +++ b/share/man/man4/pcm.4 @@ -1,680 +1,675 @@ .\" .\" Copyright (c) 2009-2011 Joel Dahl .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. .\" -.Dd March 24, 2024 +.Dd December 4, 2024 .Dt SOUND 4 .Os .Sh NAME .Nm sound , .Nm pcm , .Nm snd .Nd .Fx PCM audio device infrastructure .Sh SYNOPSIS To compile this driver into the kernel, place the following line in your kernel configuration file: .Bd -ragged -offset indent .Cd "device sound" .Ed .Sh DESCRIPTION The .Nm driver is the main component of the .Fx sound system. It works in conjunction with a bridge device driver on supported devices and provides PCM audio record and playback once it attaches. Each bridge device driver supports a specific set of audio chipsets and needs to be enabled together with the .Nm driver. PCI and ISA PnP audio devices identify themselves so users are usually not required to add anything to .Pa /boot/device.hints . .Pp Some of the main features of the .Nm driver are: multichannel audio, per-application volume control, dynamic mixing through virtual sound channels, true full duplex operation, bit perfect audio, rate conversion and low latency modes. .Pp The .Nm driver is enabled by default, along with several bridge device drivers. Those not enabled by default can be loaded during runtime with .Xr kldload 8 or during boot via .Xr loader.conf 5 . The following bridge device drivers are available: .Pp .Bl -bullet -compact .It .Xr snd_ai2s 4 (enabled by default on powerpc) .It .Xr snd_als4000 4 .It .Xr snd_atiixp 4 .It .Xr snd_cmi 4 (enabled by default on amd64, i386) .It .Xr snd_cs4281 4 .It .Xr snd_csa 4 (enabled by default on amd64, i386) .It .Xr snd_davbus 4 (enabled by default on powerpc) .It .Xr snd_emu10k1 4 .It .Xr snd_emu10kx 4 (enabled by default on amd64, i386) .It .Xr snd_envy24 4 .It .Xr snd_envy24ht 4 .It .Xr snd_es137x 4 (enabled by default on amd64, i386) .It .Xr snd_fm801 4 .It .Xr snd_hda 4 (enabled by default on amd64, i386) .It .Xr snd_hdsp 4 .It .Xr snd_hdspe 4 .It .Xr snd_ich 4 (enabled by default on amd64, i386) .It .Xr snd_maestro3 4 .It .Xr snd_neomagic 4 .It .Xr snd_solo 4 .It .Xr snd_spicds 4 .It .Xr snd_uaudio 4 (auto-loaded on device plug) .It .Xr snd_via8233 4 (enabled by default on amd64, i386) .It .Xr snd_via82c686 4 .It .Xr snd_vibes 4 .El .Pp Refer to the manual page for each bridge device driver for driver specific settings and information. .Ss Boot Variables In general, the module .Pa snd_foo corresponds to .Cd "device snd_foo" and can be loaded by the boot .Xr loader 8 via .Xr loader.conf 5 or from the command line using the .Xr kldload 8 utility. Options which can be specified in .Pa /boot/loader.conf include: .Bl -tag -width ".Va snd_driver_load" -offset indent .It Va snd_driver_load .Pq Dq Li NO If set to .Dq Li YES , this option loads all available drivers. .It Va snd_hda_load .Pq Dq Li NO If set to .Dq Li YES , only the Intel High Definition Audio bridge device driver and dependent modules will be loaded. .It Va snd_foo_load .Pq Dq Li NO If set to .Dq Li YES , load driver for card/chipset foo. .El .Pp To define default values for the different mixer channels, set the channel to the preferred value using hints, e.g.: .Va hint.pcm.0.line Ns = Ns Qq Li 0 . This will mute the input channel per default. .Ss Multichannel Audio Multichannel audio, popularly referred to as .Dq surround sound is supported and enabled by default. The .Fx multichannel matrix processor supports up to 18 interleaved channels, but the limit is currently set to 8 channels (as commonly used for 7.1 surround sound). The internal matrix mapping can handle reduction, expansion or re-routing of channels. This provides a base interface for related multichannel .Fn ioctl support. Multichannel audio works both with and without VCHANs. .Pp Most bridge device drivers are still missing multichannel matrixing support, but in most cases this should be trivial to implement. Use the .Va dev.pcm.%d.[play|rec].vchanformat .Xr sysctl 8 to adjust the number of channels used. The current multichannel interleaved structure and arrangement was implemented by inspecting various popular UNIX applications. There were no single standard, so much care has been taken to try to satisfy each possible scenario, despite the fact that each application has its own conflicting standard. .Ss EQ The Parametric Software Equalizer (EQ) enables the use of .Dq tone controls (bass and treble). Commonly used for ear-candy or frequency compensation due to the vast difference in hardware quality. EQ is disabled by default, but can be enabled with the .Va hint.pcm.%d.eq tunable. .Ss VCHANs Each device can optionally support more playback and recording channels than physical hardware provides by using .Dq virtual channels or VCHANs. VCHAN options can be configured via the .Xr sysctl 8 interface but can only be manipulated while the device is inactive. .Ss VPC .Fx supports independent and individual volume controls for each active application, without touching the master .Nm volume. This is sometimes referred to as Volume Per Channel (VPC). The VPC feature is enabled by default. .Ss Loader Tunables The following loader tunables are used to set driver configuration at the .Xr loader 8 prompt before booting the kernel, or they can be stored in .Pa /boot/loader.conf in order to automatically set them before booting the kernel. It is also possible to use .Xr kenv 1 to change these tunables before loading the .Nm driver. The following tunables can not be changed during runtime using .Xr sysctl 8 . .Bl -tag -width indent .It Va hint.pcm.%d.eq Set to 1 or 0 to explicitly enable (1) or disable (0) the equalizer. Requires a driver reload if changed. Enabling this will make bass and treble controls appear in mixer applications. This tunable is undefined by default. Equalizing is disabled by default. .It Va hint.pcm.%d.vpc Set to 1 or 0 to explicitly enable (1) or disable (0) the VPC feature. This tunable is undefined by default. VPC is however enabled by default. .El .Ss Runtime Configuration There are a number of .Xr sysctl 8 variables available which can be modified during runtime. These values can also be stored in .Pa /etc/sysctl.conf in order to automatically set them during the boot process. .Va hw.snd.* are global settings and .Va dev.pcm.* are device specific. .Bl -tag -width indent .It Va hw.snd.compat_linux_mmap Linux .Xr mmap 2 compatibility. The following values are supported (default is 0): .Bl -tag -width 2n .It -1 Force disabling/denying PROT_EXEC .Xr mmap 2 requests. .It 0 Auto detect proc/ABI type, allow .Xr mmap 2 for Linux applications, and deny for everything else. .It 1 Always allow PROT_EXEC page mappings. .El .It Va hw.snd.default_auto Automatically assign the default sound unit. The following values are supported (default is 1): .Bl -tag -width 2n .It 0 Do not assign the default sound unit automatically. .It 1 Use the best available sound device based on playing and recording capabilities of the device. .It 2 Use the most recently attached device. .El .It Va hw.snd.default_unit Default sound card for systems with multiple sound cards. When using .Xr devfs 4 , the default device for .Pa /dev/dsp . Equivalent to a symlink from .Pa /dev/dsp to .Pa /dev/dsp Ns Va ${hw.snd.default_unit} . .It Va hw.snd.feeder_eq_exact_rate Only certain rates are allowed for precise processing. The default behavior is however to allow sloppy processing for all rates, even the unsupported ones. Enable to toggle this requirement and only allow processing for supported rates. .It Va hw.snd.feeder_rate_max Maximum allowable sample rate. .It Va hw.snd.feeder_rate_min Minimum allowable sample rate. .It Va hw.snd.feeder_rate_polyphase_max Adjust to set the maximum number of allowed polyphase entries during the process of building resampling filters. Disabling polyphase resampling has the benefit of reducing memory usage, at the expense of slower and lower quality conversion. Only applicable when the SINC interpolator is used. Default value is 183040. Set to 0 to disable polyphase resampling. .It Va hw.snd.feeder_rate_quality Sample rate converter quality. Default value is 1, linear interpolation. Available options include: .Bl -tag -width 2n .It 0 Zero Order Hold, ZOH. Very fast, but with poor quality. .It 1 Linear interpolation. Fast, quality is subject to personal preference. Technically the quality is poor however, due to the lack of anti-aliasing filtering. .It 2 Bandlimited SINC interpolator. Implements polyphase banking to boost the conversion speed, at the cost of memory usage, with multiple high quality polynomial interpolators to improve the conversion accuracy. 100% fixed point, 64bit accumulator with 32bit coefficients and high precision sample buffering. Quality values are 100dB stopband, 8 taps and 85% bandwidth. .It 3 Continuation of the bandlimited SINC interpolator, with 100dB stopband, 36 taps and 90% bandwidth as quality values. .It 4 Continuation of the bandlimited SINC interprolator, with 100dB stopband, 164 taps and 97% bandwidth as quality values. .El .It Va hw.snd.feeder_rate_round Sample rate rounding threshold, to avoid large prime division at the cost of accuracy. All requested sample rates will be rounded to the nearest threshold value. Possible values range between 0 (disabled) and 500. Default is 25. .It Va hw.snd.latency Configure the buffering latency. Only affects applications that do not explicitly request blocksize / fragments. This tunable provides finer granularity than the .Va hw.snd.latency_profile tunable. Possible values range between 0 (lowest latency) and 10 (highest latency). .It Va hw.snd.latency_profile Define sets of buffering latency conversion tables for the .Va hw.snd.latency tunable. A value of 0 will use a low and aggressive latency profile which can result in possible underruns if the application cannot keep up with a rapid irq rate, especially during high workload. The default value is 1, which is considered a moderate/safe latency profile. -.It Va hw.snd.maxautovchans -Global VCHAN setting that only affects devices with at least one playback or -recording channel available. -The sound system will dynamically create up to this many VCHANs. -Set to -.Dq 0 -if no VCHANs are desired. -Maximum value is 256. +.It Va hw.snd.vchans_enable +Global VCHAN setting to enable (1) or disable (0) VCHANs. +This setting can be overridden for an individual device by using the +.Va dev.pcm.%d.[play|rec].vchans +tunables. +Default is enabled. .It Va hw.snd.report_soft_formats Controls the internal format conversion if it is available transparently to the application software. When disabled or not available, the application will only be able to select formats the device natively supports. .It Va hw.snd.report_soft_matrix Enable seamless channel matrixing even if the hardware does not support it. Makes it possible to play multichannel streams even with a simple stereo sound card. .It Va hw.snd.verbose Level of verbosity for the .Pa /dev/sndstat device. Higher values include more output and the highest level, four, should be used when reporting problems. Other options include: .Bl -tag -width 2n .It 0 Installed devices and their allocated bus resources. .It 1 The number of playback, record, virtual channels, and flags per device. .It 2 Channel information per device including the channel's current format, speed, and pseudo device statistics such as buffer overruns and buffer underruns. .It 3 File names and versions of the currently loaded sound modules. .It 4 Various messages intended for debugging. .El .It Va hw.snd.vpc_0db Default value for .Nm volume. Increase to give more room for attenuation control. Decrease for more amplification, with the possible cost of sound clipping. .It Va hw.snd.vpc_autoreset When a channel is closed the channel volume will be reset to 0db. This means that any changes to the volume will be lost. Enabling this will preserve the volume, at the cost of possible confusion when applications tries to re-open the same device. .It Va hw.snd.vpc_mixer_bypass The recommended way to use the VPC feature is to teach applications to use the correct .Fn ioctl : .Dv SNDCTL_DSP_GETPLAYVOL , SNDCTL_DSP_SETPLAYVOL , .Dv SNDCTL_DSP_SETRECVOL , SNDCTL_DSP_SETRECVOL . This is however not always possible. Enable this to allow applications to use their own existing mixer logic to control their own channel volume. .It Va hw.snd.vpc_reset Enable to restore all channel volumes back to the default value of 0db. .It Va dev.pcm.%d.bitperfect Enable or disable bitperfect mode. When enabled, channels will skip all dsp processing, such as channel matrixing, rate converting and equalizing. The pure .Nm stream will be fed directly to the hardware. If VCHANs are enabled, the bitperfect mode will use the VCHAN format/rate as the definitive format/rate target. The recommended way to use bitperfect mode is to disable VCHANs and enable this sysctl. Default is disabled. .It Va dev.pcm.%d.[play|rec].vchans -The current number of VCHANs allocated per device. -This can be set to preallocate a certain number of VCHANs. -Setting this value to -.Dq 0 -will disable VCHANs for this device. +Enable (1) or disable (0) VCHANs. +Default is enabled. .It Va dev.pcm.%d.[play|rec].vchanformat Format for VCHAN mixing. All playback paths will be converted to this format before the mixing process begins. By default only 2 channels are enabled. Available options include: .Bl -tag -width 2n .It s16le:1.0 Mono. .It s16le:2.0 Stereo, 2 channels (left, right). .It s16le:2.1 3 channels (left, right, LFE). .It s16le:3.0 3 channels (left, right, rear center). .It s16le:4.0 Quadraphonic, 4 channels (front/rear left and right). .It s16le:4.1 5 channels (4.0 + LFE). .It s16le:5.0 5 channels (4.0 + center). .It s16le:5.1 6 channels (4.0 + center + LFE). .It s16le:6.0 6 channels (4.0 + front/rear center). .It s16le:6.1 7 channels (6.0 + LFE). .It s16le:7.1 8 channels (4.0 + center + LFE + left and right side). .El .It Va dev.pcm.%d.[play|rec].vchanmode VCHAN format/rate selection. Available options include: .Bl -tag -width 2n .It fixed Channel mixing is done using fixed format/rate. Advanced operations such as digital passthrough will not work. Can be considered as a .Dq legacy mode. This is the default mode for hardware channels which lack support for digital formats. .It passthrough Channel mixing is done using fixed format/rate, but advanced operations such as digital passthrough also work. All channels will produce sound as usual until a digital format playback is requested. When this happens all other channels will be muted and the latest incoming digital format will be allowed to pass through undisturbed. Multiple concurrent digital streams are supported, but the latest stream will take precedence and mute all other streams. .It adaptive Works like the .Dq passthrough mode, but is a bit smarter, especially for multiple .Nm channels with different format/rate. When a new channel is about to start, the entire list of virtual channels will be scanned, and the channel with the best format/rate (usually the highest/biggest) will be selected. This ensures that mixing quality depends on the best channel. The downside is that the hardware DMA mode needs to be restarted, which may cause annoying pops or clicks. .El .It Va dev.pcm.%d.[play|rec].vchanrate Sample rate speed for VCHAN mixing. All playback paths will be converted to this sample rate before the mixing process begins. .It Va dev.pcm.%d.polling Experimental polling mode support where the driver operates by querying the device state on each tick using a .Xr callout 9 mechanism. Disabled by default and currently only available for a few device drivers. .El .Ss Statistics Channel statistics are only kept while the device is open. So with situations involving overruns and underruns, consider the output while the errant application is open and running. .Ss IOCTL Support The driver supports most of the OSS .Fn ioctl functions, and most applications work unmodified. A few differences exist, while memory mapped playback is supported natively and in Linux emulation, memory mapped recording is not due to VM system design. As a consequence, some applications may need to be recompiled with a slightly modified audio module. See .In sys/soundcard.h for a complete list of the supported .Fn ioctl functions. .Sh FILES The .Nm drivers may create the following device nodes: .Pp .Bl -tag -width ".Pa /dev/sndstat" -compact .It Pa /dev/dsp%d Audio device. The number represents the unit number of the device. .It Pa /dev/dsp Alias of .Pa /dev/dsp${hw.snd.default_unit} . Available only if .Pa hw.snd.basename_clone is set. .It Pa /dev/sndstat Current .Nm status, including all channels and drivers. .El .Pp All .Nm devices are listed in .Pa /dev/sndstat . Additional messages are sometimes recorded when the device is probed and attached, these messages can be viewed with the .Xr dmesg 8 utility. .Sh EXAMPLES Use the sound metadriver to load all .Nm bridge device drivers at once (for example if it is unclear which the correct driver to use is): .Pp .Dl kldload snd_driver .Pp Load a specific bridge device driver, in this case the Intel High Definition Audio driver: .Pp .Dl kldload snd_hda .Pp Check the status of all detected .Nm devices: .Pp .Dl cat /dev/sndstat .Pp Change the default sound device, in this case to the second device. This is handy if there are multiple .Nm devices available: .Pp .Dl mixer -d pcm1 .Sh DIAGNOSTICS .Bl -diag .It pcm%d:play:%d:dsp%d.p%d: play interrupt timeout, channel dead The hardware does not generate interrupts to serve incoming (play) or outgoing (record) data. .It unsupported subdevice XX A device node is not created properly. .El .Sh SEE ALSO .Xr devfs 4 , .Xr snd_ai2s 4 , .Xr snd_als4000 4 , .Xr snd_atiixp 4 , .Xr snd_cmi 4 , .Xr snd_cs4281 4 , .Xr snd_csa 4 , .Xr snd_davbus 4 , .Xr snd_emu10k1 4 , .Xr snd_emu10kx 4 , .Xr snd_envy24 4 , .Xr snd_envy24ht 4 , .Xr snd_es137x 4 , .Xr snd_fm801 4 , .Xr snd_hda 4 , .Xr snd_hdsp 4 , .Xr snd_hdspe 4 , .Xr snd_ich 4 , .Xr snd_maestro3 4 , .Xr snd_neomagic 4 , .Xr snd_solo 4 , .Xr snd_spicds 4 , .Xr snd_t4dwave 4 , .Xr snd_uaudio 4 , .Xr snd_via8233 4 , .Xr snd_via82c686 4 , .Xr snd_vibes 4 , .Xr device.hints 5 , .Xr loader.conf 5 , .Xr dmesg 8 , .Xr kldload 8 , .Xr mixer 8 , .Xr sysctl 8 .Rs .%T "Cookbook formulae for audio EQ biquad filter coefficients (Audio-EQ-Cookbook.txt), by Robert Bristow-Johnson" .%U "https://www.musicdsp.org/en/latest/Filters/197-rbj-audio-eq-cookbook.html" .Re .Rs .%T "Julius O'Smith's Digital Audio Resampling" .%U "http://ccrma.stanford.edu/~jos/resample/" .Re .Rs .%T "Polynomial Interpolators for High-Quality Resampling of Oversampled Audio, by Olli Niemitalo" .%U "http://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf" .Re .Rs .%T "The OSS API" .%U "http://www.opensound.com/pguide/oss.pdf" .Re .Sh HISTORY The .Nm device driver first appeared in .Fx 2.2.6 as .Nm pcm , written by .An Luigi Rizzo . It was later rewritten in .Fx 4.0 by .An Cameron Grant . The API evolved from the VOXWARE standard which later became OSS standard. .Sh AUTHORS .An -nosplit .An Luigi Rizzo Aq Mt luigi@iet.unipi.it initially wrote the .Nm pcm device driver and this manual page. .An Cameron Grant Aq Mt gandalf@vilnya.demon.co.uk later revised the device driver for .Fx 4.0 . .An Seigo Tanimura Aq Mt tanimura@r.dl.itc.u-tokyo.ac.jp revised this manual page. It was then rewritten for .Fx 5.2 . .Sh BUGS Some features of your sound card (e.g., global volume control) might not be supported on all devices. diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index 58315610312e..17c11dc33b7a 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -1,2641 +1,2653 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * Portions Copyright (c) Luigi Rizzo - 1997-99 * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "feeder_if.h" int report_soft_formats = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, &report_soft_formats, 0, "report software-emulated formats"); int report_soft_matrix = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_matrix, CTLFLAG_RW, &report_soft_matrix, 0, "report software-emulated channel matrixing"); int chn_latency = CHN_LATENCY_DEFAULT; static int sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) err = EINVAL; else chn_latency = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_latency, "I", "buffering latency (0=low ... 10=high)"); int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; static int sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency_profile; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX) err = EINVAL; else chn_latency_profile = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", "buffering latency profile (0=aggressive 1=safe)"); static int chn_timeout = CHN_TIMEOUT; static int sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_timeout; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX) err = EINVAL; else chn_timeout = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, timeout, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_timeout, "I", "interrupt timeout (1 - 10) seconds"); static int chn_vpc_autoreset = 1; SYSCTL_INT(_hw_snd, OID_AUTO, vpc_autoreset, CTLFLAG_RWTUN, &chn_vpc_autoreset, 0, "automatically reset channels volume to 0db"); static int chn_vol_0db_pcm = SND_VOL_0DB_PCM; static void chn_vpc_proc(int reset, int db) { struct snddev_info *d; struct pcm_channel *c; int i; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_VOL_0DB, db); if (reset != 0) chn_vpc_reset(c, SND_VOL_C_PCM, 1); CHN_UNLOCK(c); } PCM_RELEASE(d); PCM_UNLOCK(d); } } static int sysctl_hw_snd_vpc_0db(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_vol_0db_pcm; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return (err); if (val < SND_VOL_0DB_MIN || val > SND_VOL_0DB_MAX) return (EINVAL); chn_vol_0db_pcm = val; chn_vpc_proc(0, val); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_0db, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_vpc_0db, "I", "0db relative level"); static int sysctl_hw_snd_vpc_reset(SYSCTL_HANDLER_ARGS) { int err, val; val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == 0) return (err); chn_vol_0db_pcm = SND_VOL_0DB_PCM; chn_vpc_proc(1, SND_VOL_0DB_PCM); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_reset, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_vpc_reset, "I", "reset volume on all channels"); static int chn_usefrags = 0; static int chn_syncdelay = -1; SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RWTUN, &chn_usefrags, 0, "prefer setfragments() over setblocksize()"); SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RWTUN, &chn_syncdelay, 0, "append (0-1000) millisecond trailing buffer delay on each sync"); /** * @brief Channel sync group lock * * Clients should acquire this lock @b without holding any channel locks * before touching syncgroups or the main syncgroup list. */ struct mtx snd_pcm_syncgroups_mtx; MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF); /** * @brief syncgroups' master list * * Each time a channel syncgroup is created, it's added to this list. This * list should only be accessed with @sa snd_pcm_syncgroups_mtx held. * * See SNDCTL_DSP_SYNCGROUP for more information. */ struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(snd_pcm_syncgroups); static void chn_lockinit(struct pcm_channel *c, int dir) { switch (dir) { case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); cv_init(&c->intr_cv, "pcmwr"); break; case PCMDIR_PLAY_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); cv_init(&c->intr_cv, "pcmwrv"); break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); cv_init(&c->intr_cv, "pcmrd"); break; case PCMDIR_REC_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual record channel"); cv_init(&c->intr_cv, "pcmrdv"); break; default: panic("%s(): Invalid direction=%d", __func__, dir); break; } cv_init(&c->cv, "pcmchn"); } static void chn_lockdestroy(struct pcm_channel *c) { CHN_LOCKASSERT(c); CHN_BROADCAST(&c->cv); CHN_BROADCAST(&c->intr_cv); cv_destroy(&c->cv); cv_destroy(&c->intr_cv); snd_mtxfree(c->lock); } /** * @brief Determine channel is ready for I/O * * @retval 1 = ready for I/O * @retval 0 = not ready for I/O */ static int chn_polltrigger(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; u_int delta; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MMAP) { if (sndbuf_getprevtotal(bs) < c->lw) delta = c->lw; else delta = sndbuf_gettotal(bs) - sndbuf_getprevtotal(bs); } else { if (c->direction == PCMDIR_PLAY) delta = sndbuf_getfree(bs); else delta = sndbuf_getready(bs); } return ((delta < c->lw) ? 0 : 1); } static void chn_pollreset(struct pcm_channel *c) { CHN_LOCKASSERT(c); sndbuf_updateprevtotal(c->bufsoft); } static void chn_wakeup(struct pcm_channel *c) { struct snd_dbuf *bs; struct pcm_channel *ch; CHN_LOCKASSERT(c); bs = c->bufsoft; if (CHN_EMPTY(c, children.busy)) { if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) selwakeuppri(sndbuf_getsel(bs), PRIBIO); CHN_BROADCAST(&c->intr_cv); } else { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); chn_wakeup(ch); CHN_UNLOCK(ch); } } } static int chn_sleep(struct pcm_channel *c, int timeout) { int ret; CHN_LOCKASSERT(c); if (c->flags & CHN_F_DEAD) return (EINVAL); c->sleeping++; ret = cv_timedwait_sig(&c->intr_cv, c->lock, timeout); c->sleeping--; return ((c->flags & CHN_F_DEAD) ? EINVAL : ret); } /* * chn_dmaupdate() tracks the status of a dma transfer, * updating pointers. */ static unsigned int chn_dmaupdate(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; unsigned int delta, old, hwptr, amt; KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0")); CHN_LOCKASSERT(c); old = sndbuf_gethwptr(b); hwptr = chn_getptr(c); delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); sndbuf_sethwptr(b, hwptr); if (c->direction == PCMDIR_PLAY) { amt = min(delta, sndbuf_getready(b)); amt -= amt % sndbuf_getalign(b); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { amt = min(delta, sndbuf_getfree(b)); amt -= amt % sndbuf_getalign(b); if (amt > 0) sndbuf_acquire(b, NULL, amt); } if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) { device_printf(c->dev, "WARNING: %s DMA completion " "too fast/slow ! hwptr=%u, old=%u " "delta=%u amt=%u ready=%u free=%u\n", CHN_DIRSTR(c), hwptr, old, delta, amt, sndbuf_getready(b), sndbuf_getfree(b)); } return delta; } static void chn_wrfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int amt, want, wasfree; CHN_LOCKASSERT(c); if ((c->flags & CHN_F_MMAP) && !(c->flags & CHN_F_CLOSING)) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); wasfree = sndbuf_getfree(b); want = min(sndbuf_getsize(b), imax(0, sndbuf_xbytes(sndbuf_getsize(bs), bs, b) - sndbuf_getready(b))); amt = min(wasfree, want); if (amt > 0) sndbuf_feed(bs, b, c, c->feeder, amt); /* * Possible xruns. There should be no empty space left in buffer. */ if (sndbuf_getready(b) < want) c->xruns++; if (sndbuf_getfree(b) < wasfree) chn_wakeup(c); } static void chn_wrintr(struct pcm_channel *c) { CHN_LOCKASSERT(c); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from secondary to primary */ chn_wrfeed(c); /* tell the driver we've updated the primary buffer */ chn_trigger(c, PCMTRIG_EMLDMAWR); } /* * user write routine - uiomove data into secondary buffer, trigger if necessary * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_write(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getfree(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getfreeptr(bs); t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_acquire(bs, NULL, t); } ret = 0; if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) { ret = chn_start(c, 0); if (ret != 0) c->flags |= CHN_F_DEAD; } } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) { /** * @todo Evaluate whether EAGAIN is truly desirable. * 4Front drivers behave like this, but I'm * not sure if it at all violates the "write * should be allowed to block" model. * * The idea is that, while set with CHN_F_NOTRIGGER, * a channel isn't playing, *but* without this we * end up with "interrupt timeout / channel dead". */ ret = EAGAIN; } else { ret = chn_sleep(c, timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; device_printf(c->dev, "%s(): %s: " "play interrupt timeout, channel dead\n", __func__, c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return (ret); } /* * Feed new data from the read buffer. Can be called in the bottom half. */ static void chn_rdfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int amt; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MMAP) sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); amt = sndbuf_getfree(bs); if (amt > 0) sndbuf_feed(b, bs, c, c->feeder, amt); amt = sndbuf_getready(b); if (amt > 0) { c->xruns++; sndbuf_dispose(b, NULL, amt); } if (sndbuf_getready(bs) > 0) chn_wakeup(c); } /* read interrupt routine. Must be called with interrupts blocked. */ static void chn_rdintr(struct pcm_channel *c) { CHN_LOCKASSERT(c); /* tell the driver to update the primary buffer if non-dma */ chn_trigger(c, PCMTRIG_EMLDMARD); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from primary to secondary */ chn_rdfeed(c); } /* * user read routine - trigger if necessary, uiomove data from secondary buffer * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_read(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) { ret = chn_start(c, 0); if (ret != 0) { c->flags |= CHN_F_DEAD; return (ret); } } ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getready(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getreadyptr(bs); t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_dispose(bs, NULL, t); } ret = 0; } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) ret = EAGAIN; else { ret = chn_sleep(c, timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; device_printf(c->dev, "%s(): %s: " "record interrupt timeout, channel dead\n", __func__, c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return (ret); } void chn_intr_locked(struct pcm_channel *c) { CHN_LOCKASSERT(c); c->interrupts++; if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); } void chn_intr(struct pcm_channel *c) { if (CHN_LOCKOWNED(c)) { chn_intr_locked(c); return; } CHN_LOCK(c); chn_intr_locked(c); CHN_UNLOCK(c); } u_int32_t chn_start(struct pcm_channel *c, int force) { u_int32_t i, j; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; int err; CHN_LOCKASSERT(c); /* if we're running, or if we're prevented from triggering, bail */ if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force)) return (EINVAL); err = 0; if (force) { i = 1; j = 0; } else { if (c->direction == PCMDIR_REC) { i = sndbuf_getfree(bs); j = (i > 0) ? 1 : sndbuf_getready(b); } else { if (sndbuf_getfree(bs) == 0) { i = 1; j = 0; } else { struct snd_dbuf *pb; pb = CHN_BUF_PARENT(c, b); i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb); j = sndbuf_getalign(pb); } } if (snd_verbose > 3 && CHN_EMPTY(c, children)) device_printf(c->dev, "%s(): %s (%s) threshold " "i=%d j=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", i, j); } if (i >= j) { c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); if (c->flags & CHN_F_CLOSING) c->feedcount = 2; else { c->feedcount = 0; c->interrupts = 0; c->xruns = 0; } if (c->parentchannel == NULL) { if (c->direction == PCMDIR_PLAY) sndbuf_fillsilence_rl(b, sndbuf_xbytes(sndbuf_getsize(bs), bs, b)); if (snd_verbose > 3) device_printf(c->dev, "%s(): %s starting! (%s/%s) " "(ready=%d force=%d i=%d j=%d " "intrtimeout=%u latency=%dms)\n", __func__, (c->flags & CHN_F_HAS_VCHAN) ? "VCHAN PARENT" : "HW", CHN_DIRSTR(c), (c->flags & CHN_F_CLOSING) ? "closing" : "running", sndbuf_getready(b), force, i, j, c->timeout, (sndbuf_getsize(b) * 1000) / (sndbuf_getalign(b) * sndbuf_getspd(b))); } err = chn_trigger(c, PCMTRIG_START); } return (err); } void chn_resetbuf(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; c->blocks = 0; sndbuf_reset(b); sndbuf_reset(bs); } /* * chn_sync waits until the space in the given channel goes above * a threshold. The threshold is checked against fl or rl respectively. * Assume that the condition can become true, do not check here... */ int chn_sync(struct pcm_channel *c, int threshold) { struct snd_dbuf *b, *bs; int ret, count, hcount, minflush, resid, residp, syncdelay, blksz; u_int32_t cflag; CHN_LOCKASSERT(c); if (c->direction != PCMDIR_PLAY) return (EINVAL); bs = c->bufsoft; if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) || (threshold < 1 && sndbuf_getready(bs) < 1)) return (0); /* if we haven't yet started and nothing is buffered, else start*/ if (CHN_STOPPED(c)) { if (threshold > 0 || sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret != 0) return (ret); } else return (0); } b = CHN_BUF_PARENT(c, c->bufhard); minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs); syncdelay = chn_syncdelay; if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0)) minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs); /* * Append (0-1000) millisecond trailing buffer (if needed) * for slower / high latency hardwares (notably USB audio) * to avoid audible truncation. */ if (syncdelay > 0) minflush += (sndbuf_getalign(bs) * sndbuf_getspd(bs) * ((syncdelay > 1000) ? 1000 : syncdelay)) / 1000; minflush -= minflush % sndbuf_getalign(bs); if (minflush > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); minflush -= threshold; } resid = sndbuf_getready(bs); residp = resid; blksz = sndbuf_getblksz(b); if (blksz < 1) { device_printf(c->dev, "%s(): WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n", __func__, sndbuf_getmaxsize(b), sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b)); if (sndbuf_getblkcnt(b) > 0) blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b); if (blksz < 1) blksz = 1; } count = sndbuf_xbytes(minflush + resid, bs, b) / blksz; hcount = count; ret = 0; if (snd_verbose > 3) device_printf(c->dev, "%s(): [begin] timeout=%d count=%d " "minflush=%d resid=%d\n", __func__, c->timeout, count, minflush, resid); cflag = c->flags & CHN_F_CLOSING; c->flags |= CHN_F_CLOSING; while (count > 0 && (resid > 0 || minflush > 0)) { ret = chn_sleep(c, c->timeout); if (ret == ERESTART || ret == EINTR) { c->flags |= CHN_F_ABORTING; break; } else if (ret == 0 || ret == EAGAIN) { resid = sndbuf_getready(bs); if (resid == residp) { --count; if (snd_verbose > 3) device_printf(c->dev, "%s(): [stalled] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } else if (resid < residp && count < hcount) { ++count; if (snd_verbose > 3) device_printf(c->dev, "%s((): [resume] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } if (minflush > 0 && sndbuf_getfree(bs) > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); resid = sndbuf_getready(bs); minflush -= threshold; } residp = resid; } else break; } c->flags &= ~CHN_F_CLOSING; c->flags |= cflag; if (snd_verbose > 3) device_printf(c->dev, "%s(): timeout=%d count=%d hcount=%d resid=%d residp=%d " "minflush=%d ret=%d\n", __func__, c->timeout, count, hcount, resid, residp, minflush, ret); return (0); } /* called externally, handle locking */ int chn_poll(struct pcm_channel *c, int ev, struct thread *td) { struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); if (!(c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED))) { ret = chn_start(c, 1); if (ret != 0) return (0); } ret = 0; if (chn_polltrigger(c)) { chn_pollreset(c); ret = ev; } else selrecord(td, sndbuf_getsel(bs)); return (ret); } /* * chn_abort terminates a running dma transfer. it may sleep up to 200ms. * it returns the number of bytes that have not been transferred. * * called from: dsp_close, dsp_ioctl, with channel locked */ int chn_abort(struct pcm_channel *c) { int missing = 0; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); if (CHN_STOPPED(c)) return 0; c->flags |= CHN_F_ABORTING; c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); if (!(c->flags & CHN_F_VIRTUAL)) chn_dmaupdate(c); missing = sndbuf_getready(bs); c->flags &= ~CHN_F_ABORTING; return missing; } /* * this routine tries to flush the dma transfer. It is called * on a close of a playback channel. * first, if there is data in the buffer, but the dma has not yet * begun, we need to start it. * next, we wait for the play buffer to drain * finally, we stop the dma. * * called from: dsp_close, not valid for record channels. */ int chn_flush(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); c->flags |= CHN_F_CLOSING; chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); c->flags &= ~CHN_F_CLOSING; return 0; } int snd_fmtvalid(uint32_t fmt, uint32_t *fmtlist) { int i; for (i = 0; fmtlist[i] != 0; i++) { if (fmt == fmtlist[i] || ((fmt & AFMT_PASSTHROUGH) && (AFMT_ENCODING(fmt) & fmtlist[i]))) return (1); } return (0); } static const struct { char *name, *alias1, *alias2; uint32_t afmt; } afmt_tab[] = { { "alaw", NULL, NULL, AFMT_A_LAW }, { "mulaw", NULL, NULL, AFMT_MU_LAW }, { "u8", "8", NULL, AFMT_U8 }, { "s8", NULL, NULL, AFMT_S8 }, #if BYTE_ORDER == LITTLE_ENDIAN { "s16le", "s16", "16", AFMT_S16_LE }, { "s16be", NULL, NULL, AFMT_S16_BE }, #else { "s16le", NULL, NULL, AFMT_S16_LE }, { "s16be", "s16", "16", AFMT_S16_BE }, #endif { "u16le", NULL, NULL, AFMT_U16_LE }, { "u16be", NULL, NULL, AFMT_U16_BE }, { "s24le", NULL, NULL, AFMT_S24_LE }, { "s24be", NULL, NULL, AFMT_S24_BE }, { "u24le", NULL, NULL, AFMT_U24_LE }, { "u24be", NULL, NULL, AFMT_U24_BE }, #if BYTE_ORDER == LITTLE_ENDIAN { "s32le", "s32", "32", AFMT_S32_LE }, { "s32be", NULL, NULL, AFMT_S32_BE }, #else { "s32le", NULL, NULL, AFMT_S32_LE }, { "s32be", "s32", "32", AFMT_S32_BE }, #endif { "u32le", NULL, NULL, AFMT_U32_LE }, { "u32be", NULL, NULL, AFMT_U32_BE }, { "ac3", NULL, NULL, AFMT_AC3 }, { NULL, NULL, NULL, 0 } }; uint32_t snd_str2afmt(const char *req) { int ext; int ch; int i; char b1[8]; char b2[8]; memset(b1, 0, sizeof(b1)); memset(b2, 0, sizeof(b2)); i = sscanf(req, "%5[^:]:%6s", b1, b2); if (i == 1) { if (strlen(req) != strlen(b1)) return (0); strlcpy(b2, "2.0", sizeof(b2)); } else if (i == 2) { if (strlen(req) != (strlen(b1) + 1 + strlen(b2))) return (0); } else return (0); i = sscanf(b2, "%d.%d", &ch, &ext); if (i == 0) { if (strcasecmp(b2, "mono") == 0) { ch = 1; ext = 0; } else if (strcasecmp(b2, "stereo") == 0) { ch = 2; ext = 0; } else if (strcasecmp(b2, "quad") == 0) { ch = 4; ext = 0; } else return (0); } else if (i == 1) { if (ch < 1 || ch > AFMT_CHANNEL_MAX) return (0); ext = 0; } else if (i == 2) { if (ext < 0 || ext > AFMT_EXTCHANNEL_MAX) return (0); if (ch < 1 || (ch + ext) > AFMT_CHANNEL_MAX) return (0); } else return (0); for (i = 0; afmt_tab[i].name != NULL; i++) { if (strcasecmp(afmt_tab[i].name, b1) != 0) { if (afmt_tab[i].alias1 == NULL) continue; if (strcasecmp(afmt_tab[i].alias1, b1) != 0) { if (afmt_tab[i].alias2 == NULL) continue; if (strcasecmp(afmt_tab[i].alias2, b1) != 0) continue; } } /* found a match */ return (SND_FORMAT(afmt_tab[i].afmt, ch + ext, ext)); } /* not a valid format */ return (0); } uint32_t snd_afmt2str(uint32_t afmt, char *buf, size_t len) { uint32_t enc; uint32_t ext; uint32_t ch; int i; if (buf == NULL || len < AFMTSTR_LEN) return (0); memset(buf, 0, len); enc = AFMT_ENCODING(afmt); ch = AFMT_CHANNEL(afmt); ext = AFMT_EXTCHANNEL(afmt); /* check there is at least one channel */ if (ch <= ext) return (0); for (i = 0; afmt_tab[i].name != NULL; i++) { if (enc != afmt_tab[i].afmt) continue; /* found a match */ snprintf(buf, len, "%s:%d.%d", afmt_tab[i].name, ch - ext, ext); return (SND_FORMAT(enc, ch, ext)); } return (0); } int chn_reset(struct pcm_channel *c, uint32_t fmt, uint32_t spd) { int r; CHN_LOCKASSERT(c); c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; c->timeout = 1; c->xruns = 0; c->flags |= (pcm_getflags(c->dev) & SD_F_BITPERFECT) ? CHN_F_BITPERFECT : 0; r = CHANNEL_RESET(c->methods, c->devinfo); if (r == 0 && fmt != 0 && spd != 0) { r = chn_setparam(c, fmt, spd); fmt = 0; spd = 0; } if (r == 0 && fmt != 0) r = chn_setformat(c, fmt); if (r == 0 && spd != 0) r = chn_setspeed(c, spd); if (r == 0) r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); } return r; } static struct unrhdr * chn_getunr(struct snddev_info *d, int type) { switch (type) { case PCMDIR_PLAY: return (d->p_unr); case PCMDIR_PLAY_VIRTUAL: return (d->vp_unr); case PCMDIR_REC: return (d->r_unr); case PCMDIR_REC_VIRTUAL: return (d->vr_unr); default: __assert_unreachable(); } } char * chn_mkname(char *buf, size_t len, struct pcm_channel *c) { const char *str; KASSERT(buf != NULL && len != 0, ("%s(): bogus buf=%p len=%zu", __func__, buf, len)); switch (c->type) { case PCMDIR_PLAY: str = "play"; break; case PCMDIR_PLAY_VIRTUAL: str = "virtual_play"; break; case PCMDIR_REC: str = "record"; break; case PCMDIR_REC_VIRTUAL: str = "virtual_record"; break; default: __assert_unreachable(); } snprintf(buf, len, "dsp%d.%s.%d", device_get_unit(c->dev), str, c->unit); return (buf); } struct pcm_channel * chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) { struct pcm_channel *c; struct feeder_class *fc; struct snd_dbuf *b, *bs; char buf[CHN_NAMELEN]; - int i, direction; + int err, i, direction; PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); switch (dir) { case PCMDIR_PLAY: case PCMDIR_PLAY_VIRTUAL: direction = PCMDIR_PLAY; break; case PCMDIR_REC: case PCMDIR_REC_VIRTUAL: direction = PCMDIR_REC; break; default: device_printf(d->dev, "%s(): invalid channel direction: %d\n", __func__, dir); return (NULL); } PCM_UNLOCK(d); b = NULL; bs = NULL; c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); c->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); chn_lockinit(c, dir); CHN_INIT(c, children); CHN_INIT(c, children.busy); c->direction = direction; c->type = dir; c->unit = alloc_unr(chn_getunr(d, c->type)); c->format = SND_FORMAT(AFMT_S16_LE, 2, 0); c->speed = 48000; c->pid = -1; c->latency = -1; c->timeout = 1; strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); c->parentsnddev = d; c->parentchannel = parent; c->dev = d->dev; c->trigger = PCMTRIG_STOP; strlcpy(c->name, chn_mkname(buf, sizeof(buf), c), sizeof(c->name)); c->matrix = *feeder_matrix_id_map(SND_CHN_MATRIX_1_0); c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; for (i = 0; i < SND_CHN_T_MAX; i++) c->volume[SND_VOL_C_MASTER][i] = SND_VOL_0DB_MASTER; c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER; c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm; CHN_LOCK(c); chn_vpc_reset(c, SND_VOL_C_PCM, 1); CHN_UNLOCK(c); fc = feeder_getclass(NULL); if (fc == NULL) { device_printf(d->dev, "%s(): failed to get feeder class\n", __func__); goto fail; } if (feeder_add(c, fc, NULL)) { device_printf(d->dev, "%s(): failed to add feeder\n", __func__); goto fail; } b = sndbuf_create(c->dev, c->name, "primary", c); bs = sndbuf_create(c->dev, c->name, "secondary", c); if (b == NULL || bs == NULL) { device_printf(d->dev, "%s(): failed to create %s buffer\n", __func__, b == NULL ? "hardware" : "software"); goto fail; } c->bufhard = b; c->bufsoft = bs; c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, direction); if (c->devinfo == NULL) { device_printf(d->dev, "%s(): CHANNEL_INIT() failed\n", __func__); goto fail; } if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0)) { device_printf(d->dev, "%s(): hardware buffer's size is 0\n", __func__); goto fail; } sndbuf_setfmt(b, c->format); sndbuf_setspd(b, c->speed); sndbuf_setfmt(bs, c->format); sndbuf_setspd(bs, c->speed); sndbuf_setup(bs, NULL, 0); /** * @todo Should this be moved somewhere else? The primary buffer * is allocated by the driver or via DMA map setup, and tmpbuf * seems to only come into existence in sndbuf_resize(). */ if (c->direction == PCMDIR_PLAY) { bs->sl = sndbuf_getmaxsize(bs); bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_WAITOK); } + if ((c->flags & CHN_F_VIRTUAL) == 0) { + CHN_LOCK(c); + err = chn_reset(c, c->format, c->speed); + CHN_UNLOCK(c); + if (err != 0) + goto fail; + } + PCM_LOCK(d); CHN_INSERT_SORT_ASCEND(d, c, channels.pcm); + if ((c->flags & CHN_F_VIRTUAL) == 0) + CHN_INSERT_SORT_ASCEND(d, c, channels.pcm.primary); switch (c->type) { case PCMDIR_PLAY: d->playcount++; break; case PCMDIR_PLAY_VIRTUAL: d->pvchancount++; break; case PCMDIR_REC: d->reccount++; break; case PCMDIR_REC_VIRTUAL: d->rvchancount++; break; default: __assert_unreachable(); } return (c); fail: free_unr(chn_getunr(d, c->type), c->unit); feeder_remove(c); if (c->devinfo && CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); if (bs) sndbuf_destroy(bs); if (b) sndbuf_destroy(b); CHN_LOCK(c); chn_lockdestroy(c); kobj_delete(c->methods, M_DEVBUF); free(c, M_DEVBUF); PCM_LOCK(d); return (NULL); } void chn_kill(struct pcm_channel *c) { struct snddev_info *d = c->parentsnddev; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; PCM_BUSYASSERT(c->parentsnddev); PCM_LOCK(d); CHN_REMOVE(d, c, channels.pcm); + if ((c->flags & CHN_F_VIRTUAL) == 0) + CHN_REMOVE(d, c, channels.pcm.primary); switch (c->type) { case PCMDIR_PLAY: d->playcount--; break; case PCMDIR_PLAY_VIRTUAL: d->pvchancount--; break; case PCMDIR_REC: d->reccount--; break; case PCMDIR_REC_VIRTUAL: d->rvchancount--; break; default: __assert_unreachable(); } PCM_UNLOCK(d); if (CHN_STARTED(c)) { CHN_LOCK(c); chn_trigger(c, PCMTRIG_ABORT); CHN_UNLOCK(c); } free_unr(chn_getunr(c->parentsnddev, c->type), c->unit); feeder_remove(c); if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); sndbuf_destroy(bs); sndbuf_destroy(b); CHN_LOCK(c); c->flags |= CHN_F_DEAD; chn_lockdestroy(c); kobj_delete(c->methods, M_DEVBUF); free(c, M_DEVBUF); } void chn_shutdown(struct pcm_channel *c) { CHN_LOCKASSERT(c); chn_wakeup(c); c->flags |= CHN_F_DEAD; } /* release a locked channel and unlock it */ int chn_release(struct pcm_channel *c) { PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); c->flags &= ~CHN_F_BUSY; c->pid = -1; strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); CHN_UNLOCK(c); return (0); } int chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right, int center) { int i, ret; ret = 0; for (i = 0; i < SND_CHN_T_MAX; i++) { if ((1 << i) & SND_CHN_LEFT_MASK) ret |= chn_setvolume_matrix(c, vc, i, left); else if ((1 << i) & SND_CHN_RIGHT_MASK) ret |= chn_setvolume_matrix(c, vc, i, right) << 8; else ret |= chn_setvolume_matrix(c, vc, i, center) << 16; } return (ret); } int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vc == SND_VOL_C_MASTER || (vc & 1)) && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)) && (vt != SND_CHN_T_VOL_0DB || (val >= SND_VOL_0DB_MIN && val <= SND_VOL_0DB_MAX)), ("%s(): invalid volume matrix c=%p vc=%d vt=%d val=%d", __func__, c, vc, vt, val)); CHN_LOCKASSERT(c); if (val < 0) val = 0; if (val > 100) val = 100; c->volume[vc][vt] = val; /* * Do relative calculation here and store it into class + 1 * to ease the job of feeder_volume. */ if (vc == SND_VOL_C_MASTER) { for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; vc += SND_VOL_C_STEP) c->volume[SND_VOL_C_VAL(vc)][vt] = SND_VOL_CALC_VAL(c->volume, vc, vt); } else if (vc & 1) { if (vt == SND_CHN_T_VOL_0DB) for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) { c->volume[SND_VOL_C_VAL(vc)][i] = SND_VOL_CALC_VAL(c->volume, vc, i); } else c->volume[SND_VOL_C_VAL(vc)][vt] = SND_VOL_CALC_VAL(c->volume, vc, vt); } return (val); } int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt) { KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid volume matrix c=%p vc=%d vt=%d", __func__, c, vc, vt)); CHN_LOCKASSERT(c); return (c->volume[vc][vt]); } int chn_setmute_multi(struct pcm_channel *c, int vc, int mute) { int i, ret; ret = 0; for (i = 0; i < SND_CHN_T_MAX; i++) { if ((1 << i) & SND_CHN_LEFT_MASK) ret |= chn_setmute_matrix(c, vc, i, mute); else if ((1 << i) & SND_CHN_RIGHT_MASK) ret |= chn_setmute_matrix(c, vc, i, mute) << 8; else ret |= chn_setmute_matrix(c, vc, i, mute) << 16; } return (ret); } int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vc == SND_VOL_C_MASTER || (vc & 1)) && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid mute matrix c=%p vc=%d vt=%d mute=%d", __func__, c, vc, vt, mute)); CHN_LOCKASSERT(c); mute = (mute != 0); c->muted[vc][vt] = mute; /* * Do relative calculation here and store it into class + 1 * to ease the job of feeder_volume. */ if (vc == SND_VOL_C_MASTER) { for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; vc += SND_VOL_C_STEP) c->muted[SND_VOL_C_VAL(vc)][vt] = mute; } else if (vc & 1) { if (vt == SND_CHN_T_VOL_0DB) { for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) { c->muted[SND_VOL_C_VAL(vc)][i] = mute; } } else { c->muted[SND_VOL_C_VAL(vc)][vt] = mute; } } return (mute); } int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt) { KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid mute matrix c=%p vc=%d vt=%d", __func__, c, vc, vt)); CHN_LOCKASSERT(c); return (c->muted[vc][vt]); } struct pcmchan_matrix * chn_getmatrix(struct pcm_channel *c) { KASSERT(c != NULL, ("%s(): NULL channel", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (NULL); return (&c->matrix); } int chn_setmatrix(struct pcm_channel *c, struct pcmchan_matrix *m) { KASSERT(c != NULL && m != NULL, ("%s(): NULL channel or matrix", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); c->matrix = *m; c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; return (chn_setformat(c, SND_FORMAT(c->format, m->channels, m->ext))); } /* * XXX chn_oss_* exists for the sake of compatibility. */ int chn_oss_getorder(struct pcm_channel *c, unsigned long long *map) { KASSERT(c != NULL && map != NULL, ("%s(): NULL channel or map", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); return (feeder_matrix_oss_get_channel_order(&c->matrix, map)); } int chn_oss_setorder(struct pcm_channel *c, unsigned long long *map) { struct pcmchan_matrix m; int ret; KASSERT(c != NULL && map != NULL, ("%s(): NULL channel or map", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); m = c->matrix; ret = feeder_matrix_oss_set_channel_order(&m, map); if (ret != 0) return (ret); return (chn_setmatrix(c, &m)); } #define SND_CHN_OSS_FRONT (SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR) #define SND_CHN_OSS_SURR (SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR) #define SND_CHN_OSS_CENTER_LFE (SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF) #define SND_CHN_OSS_REAR (SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR) int chn_oss_getmask(struct pcm_channel *c, uint32_t *retmask) { struct pcmchan_matrix *m; struct pcmchan_caps *caps; uint32_t i, format; KASSERT(c != NULL && retmask != NULL, ("%s(): NULL channel or retmask", __func__)); CHN_LOCKASSERT(c); caps = chn_getcaps(c); if (caps == NULL || caps->fmtlist == NULL) return (ENODEV); for (i = 0; caps->fmtlist[i] != 0; i++) { format = caps->fmtlist[i]; if (!(format & AFMT_CONVERTIBLE)) { *retmask |= DSP_BIND_SPDIF; continue; } m = CHANNEL_GETMATRIX(c->methods, c->devinfo, format); if (m == NULL) continue; if (m->mask & SND_CHN_OSS_FRONT) *retmask |= DSP_BIND_FRONT; if (m->mask & SND_CHN_OSS_SURR) *retmask |= DSP_BIND_SURR; if (m->mask & SND_CHN_OSS_CENTER_LFE) *retmask |= DSP_BIND_CENTER_LFE; if (m->mask & SND_CHN_OSS_REAR) *retmask |= DSP_BIND_REAR; } /* report software-supported binding mask */ if (!CHN_BITPERFECT(c) && report_soft_matrix) *retmask |= DSP_BIND_FRONT | DSP_BIND_SURR | DSP_BIND_CENTER_LFE | DSP_BIND_REAR; return (0); } void chn_vpc_reset(struct pcm_channel *c, int vc, int force) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_BEGIN && vc <= SND_VOL_C_END, ("%s(): invalid reset c=%p vc=%d", __func__, c, vc)); CHN_LOCKASSERT(c); if (force == 0 && chn_vpc_autoreset == 0) return; for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) CHN_SETVOLUME(c, vc, i, c->volume[vc][SND_CHN_T_VOL_0DB]); } static u_int32_t round_pow2(u_int32_t v) { u_int32_t ret; if (v < 2) v = 2; ret = 0; while (v >> ret) ret++; ret = 1 << (ret - 1); while (ret < v) ret <<= 1; return ret; } static u_int32_t round_blksz(u_int32_t v, int round) { u_int32_t ret, tmp; if (round < 1) round = 1; ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1); if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2)) ret >>= 1; tmp = ret - (ret % round); while (tmp < 16 || tmp < round) { ret <<= 1; tmp = ret - (ret % round); } return ret; } /* * 4Front call it DSP Policy, while we call it "Latency Profile". The idea * is to keep 2nd buffer short so that it doesn't cause long queue during * buffer transfer. * * Latency reference table for 48khz stereo 16bit: (PLAY) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 2 | 64 | 128 | * +---------+------------+-----------+------------+ * | 1 | 4 | 128 | 512 | * +---------+------------+-----------+------------+ * | 2 | 8 | 512 | 4096 | * +---------+------------+-----------+------------+ * | 3 | 16 | 512 | 8192 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Recording need a different reference table. All we care is * gobbling up everything within reasonable buffering threshold. * * Latency reference table for 48khz stereo 16bit: (REC) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 512 | 32 | 16384 | * +---------+------------+-----------+------------+ * | 1 | 256 | 64 | 16384 | * +---------+------------+-----------+------------+ * | 2 | 128 | 128 | 16384 | * +---------+------------+-----------+------------+ * | 3 | 64 | 256 | 16384 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Calculations for other data rate are entirely based on these reference * tables. For normal operation, Latency 5 seems give the best, well * balanced performance for typical workload. Anything below 5 will * eat up CPU to keep up with increasing context switches because of * shorter buffer space and usually require the application to handle it * aggressively through possibly real time programming technique. * */ #define CHN_LATENCY_PBLKCNT_REF \ {{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}, \ {1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_PBUFSZ_REF \ {{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16}, \ {11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_RBLKCNT_REF \ {{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}, \ {9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_RBUFSZ_REF \ {{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16}, \ {15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_DATA_REF 192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */ static int chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, u_int32_t max, int *rblksz, int *rblkcnt) { static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBLKCNT_REF; static int pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBUFSZ_REF; static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBLKCNT_REF; static int rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBUFSZ_REF; u_int32_t bufsz; int lprofile, blksz, blkcnt; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || bps < 1 || datarate < 1 || !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { if (rblksz != NULL) *rblksz = CHN_2NDBUFMAXSIZE >> 1; if (rblkcnt != NULL) *rblkcnt = 2; printf("%s(): FAILED dir=%d latency=%d bps=%d " "datarate=%u max=%u\n", __func__, dir, latency, bps, datarate, max); return CHN_2NDBUFMAXSIZE; } lprofile = chn_latency_profile; if (dir == PCMDIR_PLAY) { blkcnt = pblkcnts[lprofile][latency]; bufsz = pbufszs[lprofile][latency]; } else { blkcnt = rblkcnts[lprofile][latency]; bufsz = rbufszs[lprofile][latency]; } bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, datarate)); if (bufsz > max) bufsz = max; blksz = round_blksz(bufsz >> blkcnt, bps); if (rblksz != NULL) *rblksz = blksz; if (rblkcnt != NULL) *rblkcnt = 1 << blkcnt; return blksz << blkcnt; } static int chn_resizebuf(struct pcm_channel *c, int latency, int blkcnt, int blksz) { struct snd_dbuf *b, *bs, *pb; int sblksz, sblkcnt, hblksz, hblkcnt, limit = 0, nsblksz, nsblkcnt; int ret; CHN_LOCKASSERT(c); if ((c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED)) || !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC)) return EINVAL; if (latency == -1) { c->latency = -1; latency = chn_latency; } else if (latency == -2) { latency = c->latency; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) latency = chn_latency; } else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) return EINVAL; else { c->latency = latency; } bs = c->bufsoft; b = c->bufhard; if (!(blksz == 0 || blkcnt == -1) && (blksz < 16 || blksz < sndbuf_getalign(bs) || blkcnt < 2 || (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) return EINVAL; chn_calclatency(c->direction, latency, sndbuf_getalign(bs), sndbuf_getalign(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE, &sblksz, &sblkcnt); if (blksz == 0 || blkcnt == -1) { if (blkcnt == -1) c->flags &= ~CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { blksz = sndbuf_getblksz(bs); blkcnt = sndbuf_getblkcnt(bs); } } else c->flags |= CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { /* * The application has requested their own blksz/blkcnt. * Just obey with it, and let them toast alone. We can * clamp it to the nearest latency profile, but that would * defeat the purpose of having custom control. The least * we can do is round it to the nearest ^2 and align it. */ sblksz = round_blksz(blksz, sndbuf_getalign(bs)); sblkcnt = round_pow2(blkcnt); } if (c->parentchannel != NULL) { pb = c->parentchannel->bufsoft; CHN_UNLOCK(c); CHN_LOCK(c->parentchannel); chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); CHN_UNLOCK(c->parentchannel); CHN_LOCK(c); if (c->direction == PCMDIR_PLAY) { limit = (pb != NULL) ? sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; } else { limit = (pb != NULL) ? sndbuf_xbytes(sndbuf_getblksz(pb), pb, bs) * 2 : 0; } } else { hblkcnt = 2; if (c->flags & CHN_F_HAS_SIZE) { hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b), sndbuf_getalign(b)); hblkcnt = round_pow2(sndbuf_getblkcnt(bs)); } else chn_calclatency(c->direction, latency, sndbuf_getalign(b), sndbuf_getalign(b) * sndbuf_getspd(b), CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt); if ((hblksz << 1) > sndbuf_getmaxsize(b)) hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1, sndbuf_getalign(b)); while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) { if (hblkcnt < 4) hblksz >>= 1; else hblkcnt >>= 1; } hblksz -= hblksz % sndbuf_getalign(b); CHN_UNLOCK(c); if (chn_usefrags == 0 || CHANNEL_SETFRAGMENTS(c->methods, c->devinfo, hblksz, hblkcnt) != 0) sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, hblksz)); CHN_LOCK(c); if (!CHN_EMPTY(c, children)) { nsblksz = round_blksz( sndbuf_xbytes(sndbuf_getblksz(b), b, bs), sndbuf_getalign(bs)); nsblkcnt = sndbuf_getblkcnt(b); if (c->direction == PCMDIR_PLAY) { do { nsblkcnt--; } while (nsblkcnt >= 2 && nsblksz * nsblkcnt >= sblksz * sblkcnt); nsblkcnt++; } sblksz = nsblksz; sblkcnt = nsblkcnt; limit = 0; } else limit = sndbuf_xbytes(sndbuf_getblksz(b), b, bs) * 2; } if (limit > CHN_2NDBUFMAXSIZE) limit = CHN_2NDBUFMAXSIZE; while ((sblksz * sblkcnt) < limit) sblkcnt <<= 1; while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { if (sblkcnt < 4) sblksz >>= 1; else sblkcnt >>= 1; } sblksz -= sblksz % sndbuf_getalign(bs); if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || sndbuf_getsize(bs) != (sblkcnt * sblksz)) { ret = sndbuf_remalloc(bs, sblkcnt, sblksz); if (ret != 0) { device_printf(c->dev, "%s(): Failed: %d %d\n", __func__, sblkcnt, sblksz); return ret; } } /* * Interrupt timeout */ c->timeout = ((u_int64_t)hz * sndbuf_getsize(bs)) / ((u_int64_t)sndbuf_getspd(bs) * sndbuf_getalign(bs)); if (c->parentchannel != NULL) c->timeout = min(c->timeout, c->parentchannel->timeout); if (c->timeout < 1) c->timeout = 1; /* * OSSv4 docs: "By default OSS will set the low water level equal * to the fragment size which is optimal in most cases." */ c->lw = sndbuf_getblksz(bs); chn_resetbuf(c); if (snd_verbose > 3) device_printf(c->dev, "%s(): %s (%s) timeout=%u " "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", c->timeout, sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b), sndbuf_getsize(bs), sndbuf_getblksz(bs), sndbuf_getblkcnt(bs), limit); return 0; } int chn_setlatency(struct pcm_channel *c, int latency) { CHN_LOCKASSERT(c); /* Destroy blksz/blkcnt, enforce latency profile. */ return chn_resizebuf(c, latency, -1, 0); } int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) { CHN_LOCKASSERT(c); /* Destroy latency profile, enforce blksz/blkcnt */ return chn_resizebuf(c, -1, blkcnt, blksz); } int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed) { struct pcmchan_caps *caps; uint32_t hwspeed, delta; int ret; CHN_LOCKASSERT(c); if (speed < 1 || format == 0 || CHN_STARTED(c)) return (EINVAL); c->format = format; c->speed = speed; caps = chn_getcaps(c); hwspeed = speed; RANGE(hwspeed, caps->minspeed, caps->maxspeed); sndbuf_setspd(c->bufhard, CHANNEL_SETSPEED(c->methods, c->devinfo, hwspeed)); hwspeed = sndbuf_getspd(c->bufhard); delta = (hwspeed > speed) ? (hwspeed - speed) : (speed - hwspeed); if (delta <= feeder_rate_round) c->speed = hwspeed; ret = feeder_chain(c); if (ret == 0) ret = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(c->bufhard)); if (ret == 0) ret = chn_resizebuf(c, -2, 0, 0); return (ret); } int chn_setspeed(struct pcm_channel *c, uint32_t speed) { uint32_t oldformat, oldspeed, format; int ret; oldformat = c->format; oldspeed = c->speed; format = oldformat; ret = chn_setparam(c, format, speed); if (ret != 0) { if (snd_verbose > 3) device_printf(c->dev, "%s(): Setting speed %d failed, " "falling back to %d\n", __func__, speed, oldspeed); chn_setparam(c, c->format, oldspeed); } return (ret); } int chn_setformat(struct pcm_channel *c, uint32_t format) { uint32_t oldformat, oldspeed, speed; int ret; /* XXX force stereo */ if ((format & AFMT_PASSTHROUGH) && AFMT_CHANNEL(format) < 2) { format = SND_FORMAT(format, AFMT_PASSTHROUGH_CHANNEL, AFMT_PASSTHROUGH_EXTCHANNEL); } oldformat = c->format; oldspeed = c->speed; speed = oldspeed; ret = chn_setparam(c, format, speed); if (ret != 0) { if (snd_verbose > 3) device_printf(c->dev, "%s(): Format change 0x%08x failed, " "falling back to 0x%08x\n", __func__, format, oldformat); chn_setparam(c, oldformat, oldspeed); } return (ret); } void chn_syncstate(struct pcm_channel *c) { struct snddev_info *d; struct snd_mixer *m; d = (c != NULL) ? c->parentsnddev : NULL; m = (d != NULL && d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 : NULL; if (d == NULL || m == NULL) return; CHN_LOCKASSERT(c); if (c->feederflags & (1 << FEEDER_VOLUME)) { uint32_t parent; int vol, pvol, left, right, center; if (c->direction == PCMDIR_PLAY && (d->flags & SD_F_SOFTPCMVOL)) { /* CHN_UNLOCK(c); */ vol = mix_get(m, SOUND_MIXER_PCM); parent = mix_getparent(m, SOUND_MIXER_PCM); if (parent != SOUND_MIXER_NONE) pvol = mix_get(m, parent); else pvol = 100 | (100 << 8); /* CHN_LOCK(c); */ } else { vol = 100 | (100 << 8); pvol = vol; } if (vol == -1) { device_printf(c->dev, "Soft PCM Volume: Failed to read pcm " "default value\n"); vol = 100 | (100 << 8); } if (pvol == -1) { device_printf(c->dev, "Soft PCM Volume: Failed to read parent " "default value\n"); pvol = 100 | (100 << 8); } left = ((vol & 0x7f) * (pvol & 0x7f)) / 100; right = (((vol >> 8) & 0x7f) * ((pvol >> 8) & 0x7f)) / 100; center = (left + right) >> 1; chn_setvolume_multi(c, SND_VOL_C_MASTER, left, right, center); } if (c->feederflags & (1 << FEEDER_EQ)) { struct pcm_feeder *f; int treble, bass, state; /* CHN_UNLOCK(c); */ treble = mix_get(m, SOUND_MIXER_TREBLE); bass = mix_get(m, SOUND_MIXER_BASS); /* CHN_LOCK(c); */ if (treble == -1) treble = 50; else treble = ((treble & 0x7f) + ((treble >> 8) & 0x7f)) >> 1; if (bass == -1) bass = 50; else bass = ((bass & 0x7f) + ((bass >> 8) & 0x7f)) >> 1; f = feeder_find(c, FEEDER_EQ); if (f != NULL) { if (FEEDER_SET(f, FEEDEQ_TREBLE, treble) != 0) device_printf(c->dev, "EQ: Failed to set treble -- %d\n", treble); if (FEEDER_SET(f, FEEDEQ_BASS, bass) != 0) device_printf(c->dev, "EQ: Failed to set bass -- %d\n", bass); if (FEEDER_SET(f, FEEDEQ_PREAMP, d->eqpreamp) != 0) device_printf(c->dev, "EQ: Failed to set preamp -- %d\n", d->eqpreamp); if (d->flags & SD_F_EQ_BYPASSED) state = FEEDEQ_BYPASS; else if (d->flags & SD_F_EQ_ENABLED) state = FEEDEQ_ENABLE; else state = FEEDEQ_DISABLE; if (FEEDER_SET(f, FEEDEQ_STATE, state) != 0) device_printf(c->dev, "EQ: Failed to set state -- %d\n", state); } } } int chn_trigger(struct pcm_channel *c, int go) { struct snddev_info *d = c->parentsnddev; int ret; CHN_LOCKASSERT(c); if (!PCMTRIG_COMMON(go)) return (CHANNEL_TRIGGER(c->methods, c->devinfo, go)); if (go == c->trigger) return (0); if (snd_verbose > 3) { device_printf(c->dev, "%s() %s: calling go=0x%08x , " "prev=0x%08x\n", __func__, c->name, go, c->trigger); } c->trigger = go; ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); if (ret != 0) return (ret); CHN_UNLOCK(c); PCM_LOCK(d); CHN_LOCK(c); /* * Do nothing if another thread set a different trigger while we had * dropped the mutex. */ if (go != c->trigger) { PCM_UNLOCK(d); return (0); } /* * Use the SAFE variants to prevent inserting/removing an already * existing/missing element. */ switch (go) { case PCMTRIG_START: CHN_INSERT_HEAD_SAFE(d, c, channels.pcm.busy); PCM_UNLOCK(d); chn_syncstate(c); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: CHN_REMOVE(d, c, channels.pcm.busy); PCM_UNLOCK(d); break; default: PCM_UNLOCK(d); break; } return (0); } /** * @brief Queries sound driver for sample-aligned hardware buffer pointer index * * This function obtains the hardware pointer location, then aligns it to * the current bytes-per-sample value before returning. (E.g., a channel * running in 16 bit stereo mode would require 4 bytes per sample, so a * hwptr value ranging from 32-35 would be returned as 32.) * * @param c PCM channel context * @returns sample-aligned hardware buffer pointer index */ int chn_getptr(struct pcm_channel *c) { int hwptr; CHN_LOCKASSERT(c); hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; return (hwptr - (hwptr % sndbuf_getalign(c->bufhard))); } struct pcmchan_caps * chn_getcaps(struct pcm_channel *c) { CHN_LOCKASSERT(c); return CHANNEL_GETCAPS(c->methods, c->devinfo); } u_int32_t chn_getformats(struct pcm_channel *c) { u_int32_t *fmtlist, fmts; int i; fmtlist = chn_getcaps(c)->fmtlist; fmts = 0; for (i = 0; fmtlist[i]; i++) fmts |= fmtlist[i]; /* report software-supported formats */ if (!CHN_BITPERFECT(c) && report_soft_formats) fmts |= AFMT_CONVERTIBLE; return (AFMT_ENCODING(fmts)); } int chn_notify(struct pcm_channel *c, u_int32_t flags) { struct pcm_channel *ch; struct pcmchan_caps *caps; uint32_t bestformat, bestspeed, besthwformat, *vchanformat, *vchanrate; uint32_t vpflags; int dirty, err, run, nrun; CHN_LOCKASSERT(c); if (CHN_EMPTY(c, children)) return (0); err = 0; /* * If the hwchan is running, we can't change its rate, format or * blocksize */ run = (CHN_STARTED(c)) ? 1 : 0; if (run) flags &= CHN_N_VOLUME | CHN_N_TRIGGER; if (flags & CHN_N_RATE) { /* * XXX I'll make good use of this someday. * However this is currently being superseded by * the availability of CHN_F_VCHAN_DYNAMIC. */ } if (flags & CHN_N_FORMAT) { /* * XXX I'll make good use of this someday. * However this is currently being superseded by * the availability of CHN_F_VCHAN_DYNAMIC. */ } if (flags & CHN_N_VOLUME) { /* * XXX I'll make good use of this someday, though * soft volume control is currently pretty much * integrated. */ } if (flags & CHN_N_BLOCKSIZE) { /* * Set to default latency profile */ chn_setlatency(c, chn_latency); } if ((flags & CHN_N_TRIGGER) && !(c->flags & CHN_F_VCHAN_DYNAMIC)) { nrun = CHN_EMPTY(c, children.busy) ? 0 : 1; if (nrun && !run) err = chn_start(c, 1); if (!nrun && run) chn_abort(c); flags &= ~CHN_N_TRIGGER; } if (flags & CHN_N_TRIGGER) { if (c->direction == PCMDIR_PLAY) { vchanformat = &c->parentsnddev->pvchanformat; vchanrate = &c->parentsnddev->pvchanrate; } else { vchanformat = &c->parentsnddev->rvchanformat; vchanrate = &c->parentsnddev->rvchanrate; } /* Dynamic Virtual Channel */ if (!(c->flags & CHN_F_VCHAN_ADAPTIVE)) { bestformat = *vchanformat; bestspeed = *vchanrate; } else { bestformat = 0; bestspeed = 0; } besthwformat = 0; nrun = 0; caps = chn_getcaps(c); dirty = 0; vpflags = 0; CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if ((ch->format & AFMT_PASSTHROUGH) && snd_fmtvalid(ch->format, caps->fmtlist)) { bestformat = ch->format; bestspeed = ch->speed; CHN_UNLOCK(ch); vpflags = CHN_F_PASSTHROUGH; nrun++; break; } if ((ch->flags & CHN_F_EXCLUSIVE) && vpflags == 0) { if (c->flags & CHN_F_VCHAN_ADAPTIVE) { bestspeed = ch->speed; RANGE(bestspeed, caps->minspeed, caps->maxspeed); besthwformat = snd_fmtbest(ch->format, caps->fmtlist); if (besthwformat != 0) bestformat = besthwformat; } CHN_UNLOCK(ch); vpflags = CHN_F_EXCLUSIVE; nrun++; continue; } if (!(c->flags & CHN_F_VCHAN_ADAPTIVE) || vpflags != 0) { CHN_UNLOCK(ch); nrun++; continue; } if (ch->speed > bestspeed) { bestspeed = ch->speed; RANGE(bestspeed, caps->minspeed, caps->maxspeed); } besthwformat = snd_fmtbest(ch->format, caps->fmtlist); if (!(besthwformat & AFMT_VCHAN)) { CHN_UNLOCK(ch); nrun++; continue; } if (AFMT_CHANNEL(besthwformat) > AFMT_CHANNEL(bestformat)) bestformat = besthwformat; else if (AFMT_CHANNEL(besthwformat) == AFMT_CHANNEL(bestformat) && AFMT_BIT(besthwformat) > AFMT_BIT(bestformat)) bestformat = besthwformat; CHN_UNLOCK(ch); nrun++; } if (bestformat == 0) bestformat = c->format; if (bestspeed == 0) bestspeed = c->speed; if (bestformat != c->format || bestspeed != c->speed) dirty = 1; c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE); c->flags |= vpflags; if (nrun && !run) { if (dirty) { bestspeed = CHANNEL_SETSPEED(c->methods, c->devinfo, bestspeed); err = chn_reset(c, bestformat, bestspeed); } if (err == 0 && dirty) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } } if (err == 0) { if (dirty) c->flags |= CHN_F_DIRTY; err = chn_start(c, 1); } } if (nrun && run && dirty) { chn_abort(c); bestspeed = CHANNEL_SETSPEED(c->methods, c->devinfo, bestspeed); err = chn_reset(c, bestformat, bestspeed); if (err == 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } } if (err == 0) { c->flags |= CHN_F_DIRTY; err = chn_start(c, 1); } } if (err == 0 && !(bestformat & AFMT_PASSTHROUGH) && (bestformat & AFMT_VCHAN)) { *vchanformat = bestformat; *vchanrate = bestspeed; } if (!nrun && run) { c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE); bestformat = *vchanformat; bestspeed = *vchanrate; chn_abort(c); if (c->format != bestformat || c->speed != bestspeed) chn_reset(c, bestformat, bestspeed); } } return (err); } /** * @brief Fetch array of supported discrete sample rates * * Wrapper for CHANNEL_GETRATES. Please see channel_if.m:getrates() for * detailed information. * * @note If the operation isn't supported, this function will just return 0 * (no rates in the array), and *rates will be set to NULL. Callers * should examine rates @b only if this function returns non-zero. * * @param c pcm channel to examine * @param rates pointer to array of integers; rate table will be recorded here * * @return number of rates in the array pointed to be @c rates */ int chn_getrates(struct pcm_channel *c, int **rates) { KASSERT(rates != NULL, ("rates is null")); CHN_LOCKASSERT(c); return CHANNEL_GETRATES(c->methods, c->devinfo, rates); } /** * @brief Remove channel from a sync group, if there is one. * * This function is initially intended for the following conditions: * - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl) * - Closing a device. (A channel can't be destroyed if it's still in use.) * * @note Before calling this function, the syncgroup list mutex must be * held. (Consider pcm_channel::sm protected by the SG list mutex * whether @c c is locked or not.) * * @param c channel device to be started or closed * @returns If this channel was the only member of a group, the group ID * is returned to the caller so that the caller can release it * via free_unr() after giving up the syncgroup lock. Else it * returns 0. */ int chn_syncdestroy(struct pcm_channel *c) { struct pcmchan_syncmember *sm; struct pcmchan_syncgroup *sg; int sg_id; sg_id = 0; PCM_SG_LOCKASSERT(MA_OWNED); if (c->sm != NULL) { sm = c->sm; sg = sm->parent; c->sm = NULL; KASSERT(sg != NULL, ("syncmember has null parent")); SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link); free(sm, M_DEVBUF); if (SLIST_EMPTY(&sg->members)) { SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); sg_id = sg->id; free(sg, M_DEVBUF); } } return sg_id; } #ifdef OSSV4_EXPERIMENT int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak) { CHN_LOCKASSERT(c); return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak); } #endif diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h index 6eaad8cc2c0b..31c617a6df78 100644 --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -1,450 +1,453 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ struct pcmchan_caps { u_int32_t minspeed, maxspeed; u_int32_t *fmtlist; u_int32_t caps; }; struct pcmchan_matrix { int id; uint8_t channels, ext; struct { int type; uint32_t members; } map[SND_CHN_T_MAX + 1]; uint32_t mask; int8_t offset[SND_CHN_T_MAX]; }; /* Forward declarations */ struct pcm_channel; struct pcmchan_syncgroup; struct pcmchan_syncmember; extern struct mtx snd_pcm_syncgroups_mtx; extern SLIST_HEAD(pcm_synclist, pcmchan_syncgroup) snd_pcm_syncgroups; #define PCM_SG_LOCK() mtx_lock(&snd_pcm_syncgroups_mtx) #define PCM_SG_TRYLOCK() mtx_trylock(&snd_pcm_syncgroups_mtx) #define PCM_SG_UNLOCK() mtx_unlock(&snd_pcm_syncgroups_mtx) #define PCM_SG_LOCKASSERT(arg) mtx_assert(&snd_pcm_syncgroups_mtx, arg) /** * @brief Specifies an audio device sync group */ struct pcmchan_syncgroup { SLIST_ENTRY(pcmchan_syncgroup) link; SLIST_HEAD(, pcmchan_syncmember) members; int id; /**< Group identifier; set to address of group. */ }; /** * @brief Specifies a container for members of a sync group */ struct pcmchan_syncmember { SLIST_ENTRY(pcmchan_syncmember) link; struct pcmchan_syncgroup *parent; /**< group head */ struct pcm_channel *ch; }; #define CHN_NAMELEN 32 #define CHN_COMM_UNUSED "" #define CHN_COMM_UNKNOWN "" struct pcm_channel { kobj_t methods; pid_t pid; struct pcm_feeder *feeder; u_int32_t align; int latency; u_int32_t speed; u_int32_t format; u_int32_t flags; u_int32_t feederflags; u_int64_t blocks; int direction; unsigned int interrupts, xruns, feedcount; unsigned int timeout; struct snd_dbuf *bufhard, *bufsoft; struct snddev_info *parentsnddev; struct pcm_channel *parentchannel; void *devinfo; device_t dev; int unit; int type; char name[CHN_NAMELEN]; char comm[MAXCOMLEN + 1]; struct mtx *lock; int trigger; /** * For interrupt manipulations. */ struct cv intr_cv; /** * Increment,decrement this around operations that temporarily yield * lock. */ unsigned int inprog; /* Incrememnt/decrement around cv_timedwait_sig() in chn_sleep(). */ unsigned int sleeping; /** * Special channel operations should examine @c inprog after acquiring * lock. If zero, operations may continue. Else, thread should * wait on this cv for previous operation to finish. */ struct cv cv; /** * Low water mark for select()/poll(). * * This is initialized to the channel's fragment size, and will be * overwritten if a new fragment size is set. Users may alter this * value directly with the @c SNDCTL_DSP_LOW_WATER ioctl. */ unsigned int lw; /** * If part of a sync group, this will point to the syncmember * container. */ struct pcmchan_syncmember *sm; #ifdef OSSV4_EXPERIMENT u_int16_t lpeak, rpeak; /**< Peak value from 0-32767. */ #endif struct { SLIST_HEAD(, pcm_channel) head; SLIST_ENTRY(pcm_channel) link; struct { SLIST_HEAD(, pcm_channel) head; SLIST_ENTRY(pcm_channel) link; } busy; } children; struct { struct { SLIST_ENTRY(pcm_channel) link; struct { SLIST_ENTRY(pcm_channel) link; } busy; struct { SLIST_ENTRY(pcm_channel) link; } opened; + struct { + SLIST_ENTRY(pcm_channel) link; + } primary; } pcm; } channels; struct pcmchan_matrix matrix; struct pcmchan_matrix matrix_scratch; int16_t volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; int8_t muted[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; }; #define CHN_HEAD(x, y) &(x)->y.head #define CHN_INIT(x, y) SLIST_INIT(CHN_HEAD(x, y)) #define CHN_LINK(y) y.link #define CHN_EMPTY(x, y) SLIST_EMPTY(CHN_HEAD(x, y)) #define CHN_FIRST(x, y) SLIST_FIRST(CHN_HEAD(x, y)) #define CHN_NEXT(elm, list) SLIST_NEXT((elm), CHN_LINK(list)) #define CHN_FOREACH(x, y, z) \ SLIST_FOREACH(x, CHN_HEAD(y, z), CHN_LINK(z)) #define CHN_FOREACH_SAFE(w, x, y, z) \ SLIST_FOREACH_SAFE(w, CHN_HEAD(x, z), CHN_LINK(z), y) #define CHN_INSERT_HEAD(x, y, z) \ SLIST_INSERT_HEAD(CHN_HEAD(x, z), y, CHN_LINK(z)) #define CHN_INSERT_AFTER(x, y, z) \ SLIST_INSERT_AFTER(x, y, CHN_LINK(z)) #define CHN_INSERT_HEAD_SAFE(x, y, z) do { \ struct pcm_channel *t = NULL; \ CHN_FOREACH(t, x, z) { \ if (t == y) \ break; \ } \ if (t != y) \ CHN_INSERT_HEAD(x, y, z); \ } while (0) #define CHN_INSERT_AFTER_SAFE(w, x, y, z) do { \ struct pcm_channel *t = NULL; \ CHN_FOREACH(t, w, z) { \ if (t == y) \ break; \ } \ if (t != y) \ CHN_INSERT_AFTER(x, y, z); \ } while (0) #define CHN_REMOVE(holder, elm, list) do { \ if (CHN_FIRST(holder, list) == (elm)) { \ SLIST_REMOVE_HEAD(CHN_HEAD(holder, list), CHN_LINK(list)); \ } else { \ struct pcm_channel *t = NULL; \ CHN_FOREACH(t, holder, list) { \ if (CHN_NEXT(t, list) == (elm)) { \ SLIST_REMOVE_AFTER(t, CHN_LINK(list)); \ break; \ } \ } \ } \ } while (0) #define CHN_INSERT_SORT(w, x, y, z) do { \ struct pcm_channel *t, *a = NULL; \ CHN_FOREACH(t, x, z) { \ if (((y)->type w t->type) || \ (((y)->type == t->type) && ((y)->unit w t->unit))) \ a = t; \ else \ break; \ } \ if (a != NULL) \ CHN_INSERT_AFTER(a, y, z); \ else \ CHN_INSERT_HEAD(x, y, z); \ } while (0) #define CHN_INSERT_SORT_ASCEND(x, y, z) CHN_INSERT_SORT(>, x, y, z) #define CHN_INSERT_SORT_DESCEND(x, y, z) CHN_INSERT_SORT(<, x, y, z) #define CHN_BUF_PARENT(x, y) \ (((x) != NULL && (x)->parentchannel != NULL && \ (x)->parentchannel->bufhard != NULL) ? \ (x)->parentchannel->bufhard : (y)) #include "channel_if.h" int chn_reinit(struct pcm_channel *c); int chn_write(struct pcm_channel *c, struct uio *buf); int chn_read(struct pcm_channel *c, struct uio *buf); u_int32_t chn_start(struct pcm_channel *c, int force); int chn_sync(struct pcm_channel *c, int threshold); int chn_flush(struct pcm_channel *c); int chn_poll(struct pcm_channel *c, int ev, struct thread *td); char *chn_mkname(char *buf, size_t len, struct pcm_channel *c); struct pcm_channel *chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo); void chn_kill(struct pcm_channel *c); void chn_shutdown(struct pcm_channel *c); int chn_release(struct pcm_channel *c); int chn_reset(struct pcm_channel *c, u_int32_t fmt, u_int32_t spd); int chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right, int center); int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val); int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt); int chn_setmute_multi(struct pcm_channel *c, int vc, int mute); int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute); int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt); void chn_vpc_reset(struct pcm_channel *c, int vc, int force); int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed); int chn_setspeed(struct pcm_channel *c, uint32_t speed); int chn_setformat(struct pcm_channel *c, uint32_t format); int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz); int chn_setlatency(struct pcm_channel *c, int latency); void chn_syncstate(struct pcm_channel *c); int chn_trigger(struct pcm_channel *c, int go); int chn_getptr(struct pcm_channel *c); struct pcmchan_caps *chn_getcaps(struct pcm_channel *c); u_int32_t chn_getformats(struct pcm_channel *c); struct pcmchan_matrix *chn_getmatrix(struct pcm_channel *); int chn_setmatrix(struct pcm_channel *, struct pcmchan_matrix *); int chn_oss_getorder(struct pcm_channel *, unsigned long long *); int chn_oss_setorder(struct pcm_channel *, unsigned long long *); int chn_oss_getmask(struct pcm_channel *, uint32_t *); void chn_resetbuf(struct pcm_channel *c); void chn_intr_locked(struct pcm_channel *c); void chn_intr(struct pcm_channel *c); int chn_abort(struct pcm_channel *c); int chn_notify(struct pcm_channel *c, u_int32_t flags); int chn_getrates(struct pcm_channel *c, int **rates); int chn_syncdestroy(struct pcm_channel *c); #define CHN_SETVOLUME(...) chn_setvolume_matrix(__VA_ARGS__) #if defined(SND_DIAGNOSTIC) || defined(INVARIANTS) #define CHN_GETVOLUME(...) chn_getvolume_matrix(__VA_ARGS__) #else #define CHN_GETVOLUME(x, y, z) ((x)->volume[y][z]) #endif #define CHN_GETMUTE(x, y, z) ((x)->muted[y][z]) #ifdef OSSV4_EXPERIMENT int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); #endif #define CHN_LOCKOWNED(c) mtx_owned((c)->lock) #define CHN_LOCK(c) mtx_lock((c)->lock) #define CHN_UNLOCK(c) mtx_unlock((c)->lock) #define CHN_TRYLOCK(c) mtx_trylock((c)->lock) #define CHN_LOCKASSERT(c) mtx_assert((c)->lock, MA_OWNED) #define CHN_UNLOCKASSERT(c) mtx_assert((c)->lock, MA_NOTOWNED) #define CHN_BROADCAST(x) cv_broadcastpri(x, PRIBIO) int snd_fmtvalid(uint32_t fmt, uint32_t *fmtlist); uint32_t snd_str2afmt(const char *); uint32_t snd_afmt2str(uint32_t, char *, size_t); #define AFMTSTR_LEN 16 extern int chn_latency; extern int chn_latency_profile; extern int report_soft_formats; extern int report_soft_matrix; enum { PCMDIR_PLAY = 1, PCMDIR_PLAY_VIRTUAL, PCMDIR_REC, PCMDIR_REC_VIRTUAL, }; #define PCMTRIG_START 1 #define PCMTRIG_EMLDMAWR 2 #define PCMTRIG_EMLDMARD 3 #define PCMTRIG_STOP 0 #define PCMTRIG_ABORT -1 #define PCMTRIG_COMMON(x) ((x) == PCMTRIG_START || \ (x) == PCMTRIG_STOP || \ (x) == PCMTRIG_ABORT) #define CHN_F_CLOSING 0x00000001 /* a pending close */ #define CHN_F_ABORTING 0x00000002 /* a pending abort */ #define CHN_F_RUNNING 0x00000004 /* dma is running */ #define CHN_F_TRIGGERED 0x00000008 #define CHN_F_NOTRIGGER 0x00000010 /* unused 0x00000020 */ #define CHN_F_NBIO 0x00000040 /* do non-blocking i/o */ #define CHN_F_MMAP 0x00000080 /* has been mmap()ed */ #define CHN_F_BUSY 0x00000100 /* has been opened */ #define CHN_F_DIRTY 0x00000200 /* need re-config */ #define CHN_F_DEAD 0x00000400 /* too many errors, dead, mdk */ /* unused 0x00000800 */ #define CHN_F_HAS_SIZE 0x00001000 /* user set block size */ #define CHN_F_HAS_VCHAN 0x00002000 /* vchan master */ #define CHN_F_VCHAN_PASSTHROUGH 0x00004000 /* digital ac3/dts passthrough */ #define CHN_F_VCHAN_ADAPTIVE 0x00008000 /* adaptive format/rate selection */ #define CHN_F_VCHAN_DYNAMIC (CHN_F_VCHAN_PASSTHROUGH | CHN_F_VCHAN_ADAPTIVE) #define CHN_F_VIRTUAL 0x10000000 /* not backed by hardware */ #define CHN_F_BITPERFECT 0x20000000 /* un-cooked, Heh.. */ #define CHN_F_PASSTHROUGH 0x40000000 /* passthrough re-config */ #define CHN_F_EXCLUSIVE 0x80000000 /* exclusive access */ #define CHN_F_BITS "\020" \ "\001CLOSING" \ "\002ABORTING" \ "\003RUNNING" \ "\004TRIGGERED" \ "\005NOTRIGGER" \ /* \006 */ \ "\007NBIO" \ "\010MMAP" \ "\011BUSY" \ "\012DIRTY" \ "\013DEAD" \ /* \014 */ \ "\015HAS_SIZE" \ "\016HAS_VCHAN" \ "\017VCHAN_PASSTHROUGH" \ "\020VCHAN_ADAPTIVE" \ "\035VIRTUAL" \ "\036BITPERFECT" \ "\037PASSTHROUGH" \ "\040EXCLUSIVE" #define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | \ CHN_F_VIRTUAL | CHN_F_HAS_VCHAN | \ CHN_F_VCHAN_DYNAMIC | \ CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE) #define CHN_F_MMAP_INVALID (CHN_F_DEAD | CHN_F_RUNNING) #define CHN_N_RATE 0x00000001 #define CHN_N_FORMAT 0x00000002 #define CHN_N_VOLUME 0x00000004 #define CHN_N_BLOCKSIZE 0x00000008 #define CHN_N_TRIGGER 0x00000010 #define CHN_LATENCY_MIN 0 #define CHN_LATENCY_MAX 10 #define CHN_LATENCY_DEFAULT 2 /* 21.3ms total buffering */ #define CHN_POLICY_MIN CHN_LATENCY_MIN #define CHN_POLICY_MAX CHN_LATENCY_MAX #define CHN_POLICY_DEFAULT CHN_LATENCY_DEFAULT #define CHN_LATENCY_PROFILE_MIN 0 #define CHN_LATENCY_PROFILE_MAX 1 #define CHN_LATENCY_PROFILE_DEFAULT CHN_LATENCY_PROFILE_MAX #define CHN_STARTED(c) ((c)->flags & CHN_F_TRIGGERED) #define CHN_STOPPED(c) (!CHN_STARTED(c)) #define CHN_DIRSTR(c) (((c)->direction == PCMDIR_PLAY) ? \ "PCMDIR_PLAY" : "PCMDIR_REC") #define CHN_BITPERFECT(c) ((c)->flags & CHN_F_BITPERFECT) #define CHN_PASSTHROUGH(c) ((c)->flags & CHN_F_PASSTHROUGH) #define CHN_TIMEOUT 5 #define CHN_TIMEOUT_MIN 1 #define CHN_TIMEOUT_MAX 10 /* * This should be large enough to hold all pcm data between * tsleeps in chn_{read,write} at the highest sample rate. * (which is usually 48kHz * 16bit * stereo = 192000 bytes/sec) */ #define CHN_2NDBUFBLKSIZE (2 * 1024) /* The total number of blocks per secondary bufhard. */ #define CHN_2NDBUFBLKNUM (32) /* The size of a whole secondary bufhard. */ #define CHN_2NDBUFMAXSIZE (131072) #define CHANNEL_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index dcbdd581c82a..88e0580c5c45 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -1,2952 +1,2975 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * All rights reserved. * Copyright (c) 2024 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include +#include #include #include #include #include #include #include #include #include struct dsp_cdevpriv { struct snddev_info *sc; struct pcm_channel *rdch; struct pcm_channel *wrch; struct pcm_channel *volch; }; static int dsp_mmap_allow_prot_exec = 0; SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RWTUN, &dsp_mmap_allow_prot_exec, 0, "linux mmap compatibility (-1=force disable 0=auto 1=force enable)"); static int dsp_basename_clone = 1; SYSCTL_INT(_hw_snd, OID_AUTO, basename_clone, CTLFLAG_RWTUN, &dsp_basename_clone, 0, "DSP basename cloning (0: Disable; 1: Enabled)"); #define DSP_REGISTERED(x) (PCM_REGISTERED(x) && (x)->dsp_dev != NULL) #define OLDPCM_IOCTL static d_open_t dsp_open; static d_read_t dsp_read; static d_write_t dsp_write; static d_ioctl_t dsp_ioctl; static d_poll_t dsp_poll; static d_mmap_t dsp_mmap; static d_mmap_single_t dsp_mmap_single; struct cdevsw dsp_cdevsw = { .d_version = D_VERSION, .d_open = dsp_open, .d_read = dsp_read, .d_write = dsp_write, .d_ioctl = dsp_ioctl, .d_poll = dsp_poll, .d_mmap = dsp_mmap, .d_mmap_single = dsp_mmap_single, .d_name = "dsp", }; static eventhandler_tag dsp_ehtag = NULL; static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); static int dsp_oss_syncstart(int sg_id); static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy); static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled); static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask); #ifdef OSSV4_EXPERIMENT static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif int dsp_make_dev(device_t dev) { struct make_dev_args devargs; struct snddev_info *sc; int err, unit; sc = device_get_softc(dev); unit = device_get_unit(dev); make_dev_args_init(&devargs); devargs.mda_devsw = &dsp_cdevsw; devargs.mda_uid = UID_ROOT; devargs.mda_gid = GID_WHEEL; devargs.mda_mode = 0666; devargs.mda_si_drv1 = sc; err = make_dev_s(&devargs, &sc->dsp_dev, "dsp%d", unit); if (err != 0) { device_printf(dev, "failed to create dsp%d: error %d", unit, err); return (ENXIO); } return (0); } void dsp_destroy_dev(device_t dev) { struct snddev_info *d; d = device_get_softc(dev); destroy_dev(d->dsp_dev); } static void dsp_lock_chans(struct dsp_cdevpriv *priv, uint32_t prio) { if (priv->rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_LOCK(priv->rdch); if (priv->wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_LOCK(priv->wrch); } static void dsp_unlock_chans(struct dsp_cdevpriv *priv, uint32_t prio) { if (priv->rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_UNLOCK(priv->rdch); if (priv->wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_UNLOCK(priv->wrch); } +static int +dsp_chn_alloc(struct snddev_info *d, struct pcm_channel **ch, int direction, + int flags, struct thread *td) +{ + struct pcm_channel *c; + char *comm; + pid_t pid; + int err; + bool vdir_enabled; + + KASSERT(d != NULL && ch != NULL && + (direction == PCMDIR_PLAY || direction == PCMDIR_REC), + ("%s(): invalid d=%p ch=%p direction=%d", + __func__, d, ch, direction)); + PCM_BUSYASSERT(d); + + pid = td->td_proc->p_pid; + comm = td->td_proc->p_comm; + + vdir_enabled = (direction == PCMDIR_PLAY && d->flags & SD_F_PVCHANS) || + (direction == PCMDIR_REC && d->flags & SD_F_RVCHANS); + + *ch = NULL; + CHN_FOREACH(c, d, channels.pcm.primary) { + CHN_LOCK(c); + if (c->direction != direction) { + CHN_UNLOCK(c); + continue; + } + /* Find an available primary channel to use. */ + if ((c->flags & CHN_F_BUSY) == 0 || + (vdir_enabled && (c->flags & CHN_F_HAS_VCHAN))) + break; + CHN_UNLOCK(c); + } + if (c == NULL) + return (EBUSY); + + /* + * We can have the following cases: + * - vchans are enabled, add a new vchan to the primary channel. + * - vchans are disabled, use the primary channel directly. + */ + if (vdir_enabled && ((c->flags & CHN_F_BUSY) == 0 || + c->flags & CHN_F_HAS_VCHAN)) { + err = vchan_create(c, ch); + CHN_UNLOCK(c); + if (err != 0) + return (err); + CHN_LOCK(*ch); + } else if ((c->flags & CHN_F_BUSY) == 0) { + *ch = c; + } else { + CHN_UNLOCK(c); + return (ENODEV); + } + + (*ch)->flags |= CHN_F_BUSY; + if (flags & O_NONBLOCK) + (*ch)->flags |= CHN_F_NBIO; + if (flags & O_EXCL) + (*ch)->flags |= CHN_F_EXCLUSIVE; + (*ch)->pid = pid; + strlcpy((*ch)->comm, (comm != NULL) ? comm : CHN_COMM_UNKNOWN, + sizeof((*ch)->comm)); + + if ((err = chn_reset(*ch, (*ch)->format, (*ch)->speed)) != 0) + return (err); + chn_vpc_reset(*ch, SND_VOL_C_PCM, 0); + + CHN_UNLOCK(*ch); + + return (0); +} + #define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) #define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) #define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) #define DSP_F_READ(x) ((x) & FREAD) #define DSP_F_WRITE(x) ((x) & FWRITE) static void dsp_close(void *data) { struct dsp_cdevpriv *priv = data; - struct pcm_channel *rdch, *wrch; + struct pcm_channel *rdch, *wrch, *parent; struct snddev_info *d; int sg_ids; if (priv == NULL) return; d = priv->sc; /* At this point pcm_unregister() will destroy all channels anyway. */ if (!DSP_REGISTERED(d)) goto skip; PCM_GIANT_ENTER(d); PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); rdch = priv->rdch; wrch = priv->wrch; if (rdch != NULL) CHN_REMOVE(d, rdch, channels.pcm.opened); if (wrch != NULL) CHN_REMOVE(d, wrch, channels.pcm.opened); if (rdch != NULL || wrch != NULL) { PCM_UNLOCK(d); if (rdch != NULL) { /* * The channel itself need not be locked because: * a) Adding a channel to a syncgroup happens only * in dsp_ioctl(), which cannot run concurrently * to dsp_close(). * b) The syncmember pointer (sm) is protected by * the global syncgroup list lock. * c) A channel can't just disappear, invalidating * pointers, unless it's closed/dereferenced * first. */ PCM_SG_LOCK(); sg_ids = chn_syncdestroy(rdch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); - CHN_LOCK(rdch); - chn_abort(rdch); /* won't sleep */ - rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | - CHN_F_DEAD | CHN_F_EXCLUSIVE); - chn_reset(rdch, 0, 0); - chn_release(rdch); + if (rdch->flags & CHN_F_VIRTUAL) { + parent = rdch->parentchannel; + CHN_LOCK(parent); + CHN_LOCK(rdch); + vchan_destroy(rdch); + CHN_UNLOCK(parent); + } else { + CHN_LOCK(rdch); + chn_abort(rdch); /* won't sleep */ + rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | + CHN_F_DEAD | CHN_F_EXCLUSIVE); + chn_reset(rdch, 0, 0); + chn_release(rdch); + } } if (wrch != NULL) { /* * Please see block above. */ PCM_SG_LOCK(); sg_ids = chn_syncdestroy(wrch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); - CHN_LOCK(wrch); - chn_flush(wrch); /* may sleep */ - wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | - CHN_F_DEAD | CHN_F_EXCLUSIVE); - chn_reset(wrch, 0, 0); - chn_release(wrch); + if (wrch->flags & CHN_F_VIRTUAL) { + parent = wrch->parentchannel; + CHN_LOCK(parent); + CHN_LOCK(wrch); + vchan_destroy(wrch); + CHN_UNLOCK(parent); + } else { + CHN_LOCK(wrch); + chn_flush(wrch); /* may sleep */ + wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | + CHN_F_DEAD | CHN_F_EXCLUSIVE); + chn_reset(wrch, 0, 0); + chn_release(wrch); + } } PCM_LOCK(d); } PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_LEAVE(d); skip: free(priv, M_DEVBUF); priv = NULL; } static int dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct dsp_cdevpriv *priv; - struct pcm_channel *rdch, *wrch, *ch; + struct pcm_channel *ch; struct snddev_info *d; - uint32_t fmt, spd; - int error, rderror, wrerror, dir; + int error, dir; /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) return (ENODEV); d = i_dev->si_drv1; if (!DSP_REGISTERED(d)) return (EBADF); + if (PCM_CHANCOUNT(d) >= PCM_MAXCHANS) + return (ENOMEM); + priv = malloc(sizeof(*priv), M_DEVBUF, M_WAITOK | M_ZERO); priv->sc = d; - priv->rdch = NULL; - priv->wrch = NULL; - priv->volch = NULL; error = devfs_set_cdevpriv(priv, dsp_close); if (error != 0) return (error); PCM_GIANT_ENTER(d); /* Lock snddev so nobody else can monkey with it. */ PCM_LOCK(d); PCM_WAIT(d); error = 0; if (!DSP_F_VALID(flags)) error = EINVAL; else if (!DSP_F_DUPLEX(flags) && ((DSP_F_READ(flags) && d->reccount == 0) || (DSP_F_WRITE(flags) && d->playcount == 0))) error = ENOTSUP; if (pcm_getflags(d->dev) & SD_F_SIMPLEX) { if (DSP_F_DUPLEX(flags)) { /* * If no channels are opened yet, and we request * DUPLEX, limit to playback only, otherwise open one * channel in a direction that already exists. */ if (CHN_EMPTY(d, channels.pcm.opened)) { if (d->playcount > 0) flags &= ~FREAD; else if (d->reccount > 0) flags &= ~FWRITE; } else { ch = CHN_FIRST(d, channels.pcm.opened); if (ch->direction == PCMDIR_PLAY) flags &= ~FREAD; else if (ch->direction == PCMDIR_REC) flags &= ~FWRITE; } } else if (!CHN_EMPTY(d, channels.pcm.opened)) { /* * If we requested SIMPLEX, make sure we do not open a * channel in the opposite direction. */ ch = CHN_FIRST(d, channels.pcm.opened); dir = DSP_F_READ(flags) ? PCMDIR_REC : PCMDIR_PLAY; if (ch->direction != dir) error = ENOTSUP; } } if (error != 0) { PCM_UNLOCK(d); PCM_GIANT_EXIT(d); return (error); } /* * That is just enough. Acquire and unlock pcm lock so * the other will just have to wait until we finish doing * everything. */ PCM_ACQUIRE(d); PCM_UNLOCK(d); - fmt = SND_FORMAT(AFMT_U8, 1, 0); - spd = DSP_DEFAULT_SPEED; - - rdch = NULL; - wrch = NULL; - rderror = 0; - wrerror = 0; - - if (DSP_F_READ(flags)) { - /* open for read */ - rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC, - td->td_proc->p_pid, td->td_proc->p_comm); - - if (rderror == 0 && chn_reset(rdch, fmt, spd) != 0) - rderror = ENXIO; - - if (rderror != 0) { - if (rdch != NULL) - chn_release(rdch); - if (!DSP_F_DUPLEX(flags)) { - PCM_RELEASE_QUICK(d); - PCM_GIANT_EXIT(d); - return (rderror); - } - rdch = NULL; - } else { - if (flags & O_NONBLOCK) - rdch->flags |= CHN_F_NBIO; - if (flags & O_EXCL) - rdch->flags |= CHN_F_EXCLUSIVE; - chn_vpc_reset(rdch, SND_VOL_C_PCM, 0); - CHN_UNLOCK(rdch); - } - } - if (DSP_F_WRITE(flags)) { - /* open for write */ - wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, - td->td_proc->p_pid, td->td_proc->p_comm); - - if (wrerror == 0 && chn_reset(wrch, fmt, spd) != 0) - wrerror = ENXIO; - - if (wrerror != 0) { - if (wrch != NULL) - chn_release(wrch); - if (!DSP_F_DUPLEX(flags)) { - if (rdch != NULL) { - /* - * Lock, and release previously created - * record channel - */ - CHN_LOCK(rdch); - chn_release(rdch); - } - PCM_RELEASE_QUICK(d); - PCM_GIANT_EXIT(d); - return (wrerror); - } - wrch = NULL; - } else { - if (flags & O_NONBLOCK) - wrch->flags |= CHN_F_NBIO; - if (flags & O_EXCL) - wrch->flags |= CHN_F_EXCLUSIVE; - chn_vpc_reset(wrch, SND_VOL_C_PCM, 0); - CHN_UNLOCK(wrch); + error = dsp_chn_alloc(d, &priv->wrch, PCMDIR_PLAY, flags, td); + if (error != 0) { + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (error); } + PCM_LOCK(d); + CHN_INSERT_HEAD(d, priv->wrch, channels.pcm.opened); + PCM_UNLOCK(d); } - - PCM_LOCK(d); - - if (wrch == NULL && rdch == NULL) { - PCM_RELEASE(d); + if (DSP_F_READ(flags)) { + error = dsp_chn_alloc(d, &priv->rdch, PCMDIR_REC, flags, td); + if (error != 0) { + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (error); + } + PCM_LOCK(d); + CHN_INSERT_HEAD(d, priv->rdch, channels.pcm.opened); PCM_UNLOCK(d); - PCM_GIANT_EXIT(d); - if (wrerror != 0) - return (wrerror); - if (rderror != 0) - return (rderror); - return (EINVAL); } - if (rdch != NULL) - CHN_INSERT_HEAD(d, rdch, channels.pcm.opened); - if (wrch != NULL) - CHN_INSERT_HEAD(d, wrch, channels.pcm.opened); - priv->rdch = rdch; - priv->wrch = wrch; - - PCM_RELEASE(d); - PCM_UNLOCK(d); + PCM_RELEASE_QUICK(d); PCM_GIANT_LEAVE(d); return (0); } static __inline int dsp_io_ops(struct dsp_cdevpriv *priv, struct uio *buf) { struct snddev_info *d; struct pcm_channel **ch; int (*chn_io)(struct pcm_channel *, struct uio *); int prio, ret; pid_t runpid; KASSERT(buf != NULL && (buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE), ("%s(): io train wreck!", __func__)); d = priv->sc; if (!DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); switch (buf->uio_rw) { case UIO_READ: prio = SD_F_PRIO_RD; ch = &priv->rdch; chn_io = chn_read; break; case UIO_WRITE: prio = SD_F_PRIO_WR; ch = &priv->wrch; chn_io = chn_write; break; default: panic("invalid/corrupted uio direction: %d", buf->uio_rw); break; } runpid = buf->uio_td->td_proc->p_pid; dsp_lock_chans(priv, prio); if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) { if (priv->rdch != NULL || priv->wrch != NULL) dsp_unlock_chans(priv, prio); PCM_GIANT_EXIT(d); return (EBADF); } if (((*ch)->flags & (CHN_F_MMAP | CHN_F_DEAD)) || (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) { dsp_unlock_chans(priv, prio); PCM_GIANT_EXIT(d); return (EINVAL); } else if (!((*ch)->flags & CHN_F_RUNNING)) { (*ch)->flags |= CHN_F_RUNNING; (*ch)->pid = runpid; } /* * chn_read/write must give up channel lock in order to copy bytes * from/to userland, so up the "in progress" counter to make sure * someone else doesn't come along and muss up the buffer. */ ++(*ch)->inprog; ret = chn_io(*ch, buf); --(*ch)->inprog; CHN_BROADCAST(&(*ch)->cv); dsp_unlock_chans(priv, prio); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_read(struct cdev *i_dev, struct uio *buf, int flag) { struct dsp_cdevpriv *priv; int err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); return (dsp_io_ops(priv, buf)); } static int dsp_write(struct cdev *i_dev, struct uio *buf, int flag) { struct dsp_cdevpriv *priv; int err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); return (dsp_io_ops(priv, buf)); } static int dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *volch, u_long cmd, caddr_t arg) { struct snddev_info *d; struct pcm_channel *rdch, *wrch; int j, left, right, center, mute; d = priv->sc; if (!PCM_REGISTERED(d) || !(pcm_getflags(d->dev) & SD_F_VPC)) return (-1); PCM_UNLOCKASSERT(d); j = cmd & 0xff; rdch = priv->rdch; wrch = priv->wrch; /* No specific channel, look into cache */ if (volch == NULL) volch = priv->volch; /* Look harder */ if (volch == NULL) { if (j == SOUND_MIXER_RECLEV && rdch != NULL) volch = rdch; else if (j == SOUND_MIXER_PCM && wrch != NULL) volch = wrch; } /* Final validation */ if (volch == NULL) return (EINVAL); CHN_LOCK(volch); if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { CHN_UNLOCK(volch); return (EINVAL); } switch (cmd & ~0xff) { case MIXER_WRITE(0): switch (j) { case SOUND_MIXER_MUTE: if (volch->direction == PCMDIR_REC) { chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0); } else { chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0); } break; case SOUND_MIXER_PCM: if (volch->direction != PCMDIR_PLAY) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right, center); break; case SOUND_MIXER_RECLEV: if (volch->direction != PCMDIR_REC) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right, center); break; default: /* ignore all other mixer writes */ break; } break; case MIXER_READ(0): switch (j) { case SOUND_MIXER_MUTE: mute = CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FL) || CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FR); if (volch->direction == PCMDIR_REC) { *(int *)arg = mute << SOUND_MIXER_RECLEV; } else { *(int *)arg = mute << SOUND_MIXER_PCM; } break; case SOUND_MIXER_PCM: if (volch->direction != PCMDIR_PLAY) break; *(int *)arg = CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_RECLEV: if (volch->direction != PCMDIR_REC) break; *(int *)arg = CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: if (volch->direction == PCMDIR_REC) *(int *)arg = SOUND_MASK_RECLEV; else *(int *)arg = SOUND_MASK_PCM; break; default: *(int *)arg = 0; break; } break; default: break; } CHN_UNLOCK(volch); return (0); } static int dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct dsp_cdevpriv *priv; struct pcm_channel *chn, *rdch, *wrch; struct snddev_info *d; u_long xcmd; int *arg_i, ret, tmp, err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (!DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); arg_i = (int *)arg; ret = 0; xcmd = 0; chn = NULL; if (IOCGROUP(cmd) == 'M') { if (cmd == OSS_GETVERSION) { *arg_i = SOUND_VERSION; PCM_GIANT_EXIT(d); return (0); } ret = dsp_ioctl_channel(priv, priv->volch, cmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = EBADF; PCM_GIANT_EXIT(d); return (ret); } /* * Certain ioctls may be made on any type of device (audio, mixer, * and MIDI). Handle those special cases here. */ if (IOCGROUP(cmd) == 'X') { PCM_ACQUIRE_QUICK(d); switch(cmd) { case SNDCTL_SYSINFO: sound_oss_sysinfo((oss_sysinfo *)arg); break; case SNDCTL_CARDINFO: ret = sound_oss_card_info((oss_card_info *)arg); break; case SNDCTL_AUDIOINFO: ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, false); break; case SNDCTL_AUDIOINFO_EX: ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, true); break; case SNDCTL_ENGINEINFO: ret = dsp_oss_engineinfo(i_dev, (oss_audioinfo *)arg); break; case SNDCTL_MIXERINFO: ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); break; default: ret = EINVAL; } PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (ret); } rdch = priv->rdch; wrch = priv->wrch; if (wrch != NULL && (wrch->flags & CHN_F_DEAD)) wrch = NULL; if (rdch != NULL && (rdch->flags & CHN_F_DEAD)) rdch = NULL; if (wrch == NULL && rdch == NULL) { PCM_GIANT_EXIT(d); return (EINVAL); } switch(cmd) { #ifdef OLDPCM_IOCTL /* * we start with the new ioctl interface. */ case AIONWRITE: /* how many bytes can write ? */ if (wrch) { CHN_LOCK(wrch); /* if (wrch && wrch->bufhard.dl) while (chn_wrfeed(wrch) == 0); */ *arg_i = sndbuf_getfree(wrch->bufsoft); CHN_UNLOCK(wrch); } else { *arg_i = 0; ret = EINVAL; } break; case AIOSSIZE: /* set the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; p->play_size = 0; p->rec_size = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, p->play_size); p->play_size = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, p->rec_size); p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); } break; case AIOGSIZE: /* get the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; if (wrch) { CHN_LOCK(wrch); p->play_size = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } } break; case AIOSFMT: case AIOGFMT: { snd_chan_param *p = (snd_chan_param *)arg; if (cmd == AIOSFMT && ((p->play_format != 0 && p->play_rate == 0) || (p->rec_format != 0 && p->rec_rate == 0))) { ret = EINVAL; break; } PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); if (cmd == AIOSFMT && p->play_format != 0) { chn_setformat(wrch, SND_FORMAT(p->play_format, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); chn_setspeed(wrch, p->play_rate); } p->play_rate = wrch->speed; p->play_format = AFMT_ENCODING(wrch->format); CHN_UNLOCK(wrch); } else { p->play_rate = 0; p->play_format = 0; } if (rdch) { CHN_LOCK(rdch); if (cmd == AIOSFMT && p->rec_format != 0) { chn_setformat(rdch, SND_FORMAT(p->rec_format, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); chn_setspeed(rdch, p->rec_rate); } p->rec_rate = rdch->speed; p->rec_format = AFMT_ENCODING(rdch->format); CHN_UNLOCK(rdch); } else { p->rec_rate = 0; p->rec_format = 0; } PCM_RELEASE_QUICK(d); } break; case AIOGCAP: /* get capabilities */ { snd_capabilities *p = (snd_capabilities *)arg; struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; struct cdev *pdev; PCM_LOCK(d); if (rdch) { CHN_LOCK(rdch); rcaps = chn_getcaps(rdch); } if (wrch) { CHN_LOCK(wrch); pcaps = chn_getcaps(wrch); } p->rate_min = max(rcaps? rcaps->minspeed : 0, pcaps? pcaps->minspeed : 0); p->rate_max = min(rcaps? rcaps->maxspeed : 1000000, pcaps? pcaps->maxspeed : 1000000); p->bufsize = min(rdch? sndbuf_getsize(rdch->bufsoft) : 1000000, wrch? sndbuf_getsize(wrch->bufsoft) : 1000000); /* XXX bad on sb16 */ p->formats = (rdch? chn_getformats(rdch) : 0xffffffff) & (wrch? chn_getformats(wrch) : 0xffffffff); if (rdch && wrch) { p->formats |= (pcm_getflags(d->dev) & SD_F_SIMPLEX) ? 0 : AFMT_FULLDUPLEX; } pdev = d->mixer_dev; p->mixers = 1; /* default: one mixer */ p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0; p->left = p->right = 100; if (wrch) CHN_UNLOCK(wrch); if (rdch) CHN_UNLOCK(rdch); PCM_UNLOCK(d); } break; case AIOSTOP: if (*arg_i == AIOSYNC_PLAY && wrch) { CHN_LOCK(wrch); *arg_i = chn_abort(wrch); CHN_UNLOCK(wrch); } else if (*arg_i == AIOSYNC_CAPTURE && rdch) { CHN_LOCK(rdch); *arg_i = chn_abort(rdch); CHN_UNLOCK(rdch); } else { printf("AIOSTOP: bad channel 0x%x\n", *arg_i); *arg_i = 0; } break; case AIOSYNC: printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n", ((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos); break; #endif /* * here follow the standard ioctls (filio.h etc.) */ case FIONREAD: /* get # bytes to read */ if (rdch) { CHN_LOCK(rdch); /* if (rdch && rdch->bufhard.dl) while (chn_rdfeed(rdch) == 0); */ *arg_i = sndbuf_getready(rdch->bufsoft); CHN_UNLOCK(rdch); } else { *arg_i = 0; ret = EINVAL; } break; case FIOASYNC: /*set/clear async i/o */ DEB( printf("FIOASYNC\n") ; ) break; case SNDCTL_DSP_NONBLOCK: /* set non-blocking i/o */ case FIONBIO: /* set/clear non-blocking i/o */ if (rdch) { CHN_LOCK(rdch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) rdch->flags |= CHN_F_NBIO; else rdch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) wrch->flags |= CHN_F_NBIO; else wrch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(wrch); } break; /* * Finally, here is the linux-compatible ioctl interface */ #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int) case THE_REAL_SNDCTL_DSP_GETBLKSIZE: case SNDCTL_DSP_GETBLKSIZE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = sndbuf_getblksz(chn->bufsoft); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETBLKSIZE: RANGE(*arg_i, 16, 65536); PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, *arg_i); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, *arg_i); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_RESET: DEB(printf("dsp reset\n")); if (wrch) { CHN_LOCK(wrch); chn_abort(wrch); chn_resetbuf(wrch); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_abort(rdch); chn_resetbuf(rdch); CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_SYNC: DEB(printf("dsp sync\n")); /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SPEED: /* chn_setspeed may sleep */ tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setspeed(wrch, *arg_i); tmp = wrch->speed; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setspeed(rdch, *arg_i); if (tmp == 0) tmp = rdch->speed; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_READ_RATE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn->speed; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_STEREO: tmp = -1; *arg_i = (*arg_i)? 2 : 1; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, 0)); tmp = (AFMT_CHANNEL(wrch->format) > 1)? 1 : 0; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, 0)); if (tmp == -1) tmp = (AFMT_CHANNEL(rdch->format) > 1)? 1 : 0; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_WRITE_CHANNELS: /* case SNDCTL_DSP_CHANNELS: ( == SOUND_PCM_WRITE_CHANNELS) */ if (*arg_i < 0 || *arg_i > AFMT_CHANNEL_MAX) { *arg_i = 0; ret = EINVAL; break; } if (*arg_i != 0) { uint32_t ext = 0; tmp = 0; /* * Map channel number to surround sound formats. * Devices that need bitperfect mode to operate * (e.g. more than SND_CHN_MAX channels) are not * subject to any mapping. */ if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT)) { struct pcmchan_matrix *m; if (*arg_i > SND_CHN_MAX) *arg_i = SND_CHN_MAX; m = feeder_matrix_default_channel_map(*arg_i); if (m != NULL) ext = m->ext; } PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, ext)); tmp = AFMT_CHANNEL(wrch->format); CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, ext)); if (tmp == 0) tmp = AFMT_CHANNEL(rdch->format); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } break; case SOUND_PCM_READ_CHANNELS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn_getformats(chn); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ if (*arg_i != AFMT_QUERY) { tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(*arg_i, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); tmp = wrch->format; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(*arg_i, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); if (tmp == 0) tmp = rdch->format; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = AFMT_ENCODING(tmp); } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_ENCODING(chn->format); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_SETFRAGMENT: DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); { uint32_t fragln = (*arg_i) & 0x0000ffff; uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; uint32_t fragsz; uint32_t r_maxfrags, r_fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; if (maxfrags == 0) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; if (maxfrags < 2) maxfrags = 2; if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); PCM_ACQUIRE_QUICK(d); if (rdch) { CHN_LOCK(rdch); ret = chn_setblocksize(rdch, maxfrags, fragsz); r_maxfrags = sndbuf_getblkcnt(rdch->bufsoft); r_fragsz = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } else { r_maxfrags = maxfrags; r_fragsz = fragsz; } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setblocksize(wrch, maxfrags, fragsz); maxfrags = sndbuf_getblkcnt(wrch->bufsoft); fragsz = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } else { /* use whatever came from the read channel */ maxfrags = r_maxfrags; fragsz = r_fragsz; } PCM_RELEASE_QUICK(d); fragln = 0; while (fragsz > 1) { fragln++; fragsz >>= 1; } *arg_i = (maxfrags << 16) | fragln; } break; case SNDCTL_DSP_GETISPACE: /* return the size of data available in the input queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); a->bytes = sndbuf_getready(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOSPACE: /* return space available in the output queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); a->bytes = sndbuf_getfree(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETIPTR: { count_info *a = (count_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - rdch->blocks; a->ptr = sndbuf_getfreeptr(bs); rdch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOPTR: { count_info *a = (count_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - wrch->blocks; a->ptr = sndbuf_getreadyptr(bs); wrch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETCAPS: PCM_LOCK(d); *arg_i = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; if (rdch && wrch && !(pcm_getflags(d->dev) & SD_F_SIMPLEX)) *arg_i |= PCM_CAP_DUPLEX; if (rdch && (rdch->flags & CHN_F_VIRTUAL) != 0) *arg_i |= PCM_CAP_VIRTUAL; if (wrch && (wrch->flags & CHN_F_VIRTUAL) != 0) *arg_i |= PCM_CAP_VIRTUAL; PCM_UNLOCK(d); break; case SOUND_PCM_READ_BITS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); if (chn->format & AFMT_8BIT) *arg_i = 8; else if (chn->format & AFMT_16BIT) *arg_i = 16; else if (chn->format & AFMT_24BIT) *arg_i = 24; else if (chn->format & AFMT_32BIT) *arg_i = 32; else ret = EINVAL; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETTRIGGER: if (rdch) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_INPUT) chn_start(rdch, 1); else { chn_abort(rdch); chn_resetbuf(rdch); rdch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_OUTPUT) chn_start(wrch, 1); else { chn_abort(wrch); chn_resetbuf(wrch); wrch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_GETTRIGGER: *arg_i = 0; if (wrch) { CHN_LOCK(wrch); if (wrch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_OUTPUT; CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); if (rdch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_INPUT; CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_GETODELAY: if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; break; case SNDCTL_DSP_POST: if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; chn_start(wrch, 1); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SETDUPLEX: /* * switch to full-duplex mode if card is in half-duplex * mode and is able to work in full-duplex mode */ PCM_LOCK(d); if (rdch && wrch && (pcm_getflags(d->dev) & SD_F_SIMPLEX)) pcm_setflags(d->dev, pcm_getflags(d->dev)^SD_F_SIMPLEX); PCM_UNLOCK(d); break; /* * The following four ioctls are simple wrappers around mixer_ioctl * with no further processing. xcmd is short for "translated * command". */ case SNDCTL_DSP_GETRECVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_READ_RECLEV; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETRECVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_RECLEV; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_GETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_READ_PCM; chn = wrch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_PCM; chn = wrch; } ret = dsp_ioctl_channel(priv, chn, xcmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = ENOTSUP; break; case SNDCTL_DSP_GET_RECSRC_NAMES: case SNDCTL_DSP_GET_RECSRC: case SNDCTL_DSP_SET_RECSRC: if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = ENOTSUP; break; /* * The following 3 ioctls aren't very useful at the moment. For * now, only a single channel is associated with a cdev (/dev/dspN * instance), so there's only a single output routing to use (i.e., * the wrch bound to this cdev). */ case SNDCTL_DSP_GET_PLAYTGT_NAMES: { oss_mixer_enuminfo *ei; ei = (oss_mixer_enuminfo *)arg; ei->dev = 0; ei->ctrl = 0; ei->version = 0; /* static for now */ ei->strindex[0] = 0; if (wrch != NULL) { ei->nvalues = 1; strlcpy(ei->strings, wrch->name, sizeof(ei->strings)); } else { ei->nvalues = 0; ei->strings[0] = '\0'; } } break; case SNDCTL_DSP_GET_PLAYTGT: case SNDCTL_DSP_SET_PLAYTGT: /* yes, they are the same for now */ /* * Re: SET_PLAYTGT * OSSv4: "The value that was accepted by the device will * be returned back in the variable pointed by the * argument." */ if (wrch != NULL) *arg_i = 0; else ret = EINVAL; break; case SNDCTL_DSP_SILENCE: /* * Flush the software (pre-feed) buffer, but try to minimize playback * interruption. (I.e., record unplayed samples with intent to * restore by SNDCTL_DSP_SKIP.) Intended for application "pause" * functionality. */ if (wrch == NULL) ret = EINVAL; else { struct snd_dbuf *bs; CHN_LOCK(wrch); while (wrch->inprog != 0) cv_wait(&wrch->cv, wrch->lock); bs = wrch->bufsoft; if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) { bs->sl = sndbuf_getready(bs); sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs)); sndbuf_fillsilence(bs); chn_start(wrch, 0); } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SKIP: /* * OSSv4 docs: "This ioctl call discards all unplayed samples in the * playback buffer by moving the current write position immediately * before the point where the device is currently reading the samples." */ if (wrch == NULL) ret = EINVAL; else { struct snd_dbuf *bs; CHN_LOCK(wrch); while (wrch->inprog != 0) cv_wait(&wrch->cv, wrch->lock); bs = wrch->bufsoft; if ((bs->shadbuf != NULL) && (bs->sl > 0)) { sndbuf_softreset(bs); sndbuf_acquire(bs, bs->shadbuf, bs->sl); bs->sl = 0; chn_start(wrch, 0); } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_CURRENT_OPTR: case SNDCTL_DSP_CURRENT_IPTR: /** * @note Changing formats resets the buffer counters, which differs * from the 4Front drivers. However, I don't expect this to be * much of a problem. * * @note In a test where @c CURRENT_OPTR is called immediately after write * returns, this driver is about 32K samples behind whereas * 4Front's is about 8K samples behind. Should determine source * of discrepancy, even if only out of curiosity. * * @todo Actually test SNDCTL_DSP_CURRENT_IPTR. */ chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { struct snd_dbuf *bs; /* int tmp; */ oss_count_t *oc = (oss_count_t *)arg; CHN_LOCK(chn); bs = chn->bufsoft; oc->samples = sndbuf_gettotal(bs) / sndbuf_getalign(bs); oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getalign(bs); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_HALT_OUTPUT: case SNDCTL_DSP_HALT_INPUT: chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { CHN_LOCK(chn); chn_abort(chn); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_LOW_WATER: /* * Set the number of bytes required to attract attention by * select/poll. */ if (wrch != NULL) { CHN_LOCK(wrch); wrch->lw = (*arg_i > 1) ? *arg_i : 1; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); rdch->lw = (*arg_i > 1) ? *arg_i : 1; CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_GETERROR: /* * OSSv4 docs: "All errors and counters will automatically be * cleared to zeroes after the call so each call will return only * the errors that occurred after the previous invocation. ... The * play_underruns and rec_overrun fields are the only useful fields * returned by OSS 4.0." */ { audio_errinfo *ei = (audio_errinfo *)arg; bzero((void *)ei, sizeof(*ei)); if (wrch != NULL) { CHN_LOCK(wrch); ei->play_underruns = wrch->xruns; wrch->xruns = 0; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); ei->rec_overruns = rdch->xruns; rdch->xruns = 0; CHN_UNLOCK(rdch); } } break; case SNDCTL_DSP_SYNCGROUP: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SYNCSTART: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncstart(*arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_POLICY: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_policy(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_COOKEDMODE: PCM_ACQUIRE_QUICK(d); if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT)) ret = dsp_oss_cookedmode(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GETCHANNELMASK: /* XXX vlc */ PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchannelmask(wrch, rdch, (int *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_BIND_CHANNEL: /* XXX what?!? */ ret = EINVAL; break; #ifdef OSSV4_EXPERIMENT /* * XXX The following ioctls are not yet supported and just return * EINVAL. */ case SNDCTL_DSP_GETOPEAKS: case SNDCTL_DSP_GETIPEAKS: chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { oss_peaks_t *op = (oss_peaks_t *)arg; int lpeak, rpeak; CHN_LOCK(chn); ret = chn_getpeaks(chn, &lpeak, &rpeak); if (ret == -1) ret = EINVAL; else { (*op)[0] = lpeak; (*op)[1] = rpeak; } CHN_UNLOCK(chn); } break; /* * XXX Once implemented, revisit this for proper cv protection * (if necessary). */ case SNDCTL_GETLABEL: ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg); break; case SNDCTL_SETLABEL: ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg); break; case SNDCTL_GETSONG: ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg); break; case SNDCTL_SETSONG: ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg); break; case SNDCTL_SETNAME: ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg); break; #endif /* !OSSV4_EXPERIMENT */ case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: /* undocumented */ case SNDCTL_DSP_SUBDIVIDE: case SOUND_PCM_WRITE_FILTER: case SOUND_PCM_READ_FILTER: /* dunno what these do, don't sound important */ default: DEB(printf("default ioctl fn 0x%08lx fail\n", cmd)); ret = EINVAL; break; } PCM_GIANT_LEAVE(d); return (ret); } static int dsp_poll(struct cdev *i_dev, int events, struct thread *td) { struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch; int ret, e, err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (!DSP_REGISTERED(d)) { /* XXX many clients don't understand POLLNVAL */ return (events & (POLLHUP | POLLPRI | POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)); } PCM_GIANT_ENTER(d); ret = 0; dsp_lock_chans(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); wrch = priv->wrch; rdch = priv->rdch; if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) { e = (events & (POLLOUT | POLLWRNORM)); if (e) ret |= chn_poll(wrch, e, td); } if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) { e = (events & (POLLIN | POLLRDNORM)); if (e) ret |= chn_poll(rdch, e, td); } dsp_unlock_chans(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { /* * offset is in range due to checks in dsp_mmap_single(). * XXX memattr is not honored. */ *paddr = vtophys(offset); return (0); } static int dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, vm_size_t size, struct vm_object **object, int nprot) { struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch, *c; int err; /* * Reject PROT_EXEC by default. It just doesn't makes sense. * Unfortunately, we have to give up this one due to linux_mmap * changes. * * https://lists.freebsd.org/pipermail/freebsd-emulation/2007-June/003698.html * */ #ifdef SV_ABI_LINUX if ((nprot & PROT_EXEC) && (dsp_mmap_allow_prot_exec < 0 || (dsp_mmap_allow_prot_exec == 0 && SV_CURPROC_ABI() != SV_ABI_LINUX))) #else if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec < 1) #endif return (EINVAL); /* * PROT_READ (alone) selects the input buffer. * PROT_WRITE (alone) selects the output buffer. * PROT_WRITE|PROT_READ together select the output buffer. */ if ((nprot & (PROT_READ | PROT_WRITE)) == 0) return (EINVAL); if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (!DSP_REGISTERED(d)) return (EINVAL); PCM_GIANT_ENTER(d); dsp_lock_chans(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); wrch = priv->wrch; rdch = priv->rdch; c = ((nprot & PROT_WRITE) != 0) ? wrch : rdch; if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) || (*offset + size) > sndbuf_getallocsize(c->bufsoft) || (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) || (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) { dsp_unlock_chans(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); PCM_GIANT_EXIT(d); return (EINVAL); } if (wrch != NULL) wrch->flags |= CHN_F_MMAP; if (rdch != NULL) rdch->flags |= CHN_F_MMAP; *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset); dsp_unlock_chans(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); *object = vm_pager_allocate(OBJT_DEVICE, i_dev, size, nprot, *offset, curthread->td_ucred); PCM_GIANT_LEAVE(d); if (*object == NULL) return (EINVAL); return (0); } static const char *dsp_aliases[] = { "dsp_ac3", "dsp_mmap", "dsp_multich", "dsp_spdifout", "dsp_spdifin", }; static void dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev) { struct snddev_info *d; size_t i; if (*dev != NULL) return; if (strcmp(name, "dsp") == 0 && dsp_basename_clone) goto found; for (i = 0; i < nitems(dsp_aliases); i++) { if (strcmp(name, dsp_aliases[i]) == 0) goto found; } return; found: bus_topo_lock(); d = devclass_get_softc(pcm_devclass, snd_unit); /* * If we only have a single soundcard attached and we detach it right * before entering dsp_clone(), there is a chance pcm_unregister() will * have returned already, meaning it will have set snd_unit to -1, and * thus devclass_get_softc() will return NULL here. */ if (DSP_REGISTERED(d)) { *dev = d->dsp_dev; dev_ref(*dev); } bus_topo_unlock(); } static void dsp_sysinit(void *p) { if (dsp_ehtag != NULL) return; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { if (dsp_ehtag == NULL) return; EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); dsp_ehtag = NULL; } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); static void dsp_oss_audioinfo_unavail(oss_audioinfo *ai, int unit) { bzero(ai, sizeof(*ai)); ai->dev = unit; snprintf(ai->name, sizeof(ai->name), "pcm%d (unavailable)", unit); ai->pid = -1; strlcpy(ai->cmd, CHN_COMM_UNUSED, sizeof(ai->cmd)); ai->card_number = unit; ai->port_number = unit; ai->mixer_dev = -1; ai->legacy_device = unit; } /** * @brief Handler for SNDCTL_AUDIOINFO. * * Gathers information about the audio device specified in ai->dev. If * ai->dev == -1, then this function gathers information about the current * device. If the call comes in on a non-audio device and ai->dev == -1, * return EINVAL. * * This routine is supposed to go practically straight to the hardware, * getting capabilities directly from the sound card driver, side-stepping * the intermediate channel interface. * * @note * Calling threads must not hold any snddev_info or pcm_channel locks. * * @param dev device on which the ioctl was issued * @param ai ioctl request data container * @param ex flag to distinguish between SNDCTL_AUDIOINFO from * SNDCTL_AUDIOINFO_EX * * @retval 0 success * @retval EINVAL ai->dev specifies an invalid device */ int dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai, bool ex) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, minch, maxch, unit; /* * If probing the device that received the ioctl, make sure it's a * DSP device. (Users may use this ioctl with /dev/mixer and * /dev/midi.) */ if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) return (EINVAL); for (unit = 0; pcm_devclass != NULL && unit < devclass_get_maxunit(pcm_devclass); unit++) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d)) { if ((ai->dev == -1 && unit == snd_unit) || ai->dev == unit) { dsp_oss_audioinfo_unavail(ai, unit); return (0); } else { d = NULL; continue; } } PCM_UNLOCKASSERT(d); PCM_LOCK(d); if ((ai->dev == -1 && d->dsp_dev == i_dev) || (ai->dev == unit)) { PCM_UNLOCK(d); break; } else { PCM_UNLOCK(d); d = NULL; } } /* Exhausted the search -- nothing is locked, so return. */ if (d == NULL) return (EINVAL); /* XXX Need Giant magic entry ??? */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); bzero((void *)ai, sizeof(oss_audioinfo)); ai->dev = unit; strlcpy(ai->name, device_get_desc(d->dev), sizeof(ai->name)); ai->pid = -1; strlcpy(ai->cmd, CHN_COMM_UNKNOWN, sizeof(ai->cmd)); ai->card_number = unit; ai->port_number = unit; ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; ai->legacy_device = unit; snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); ai->enabled = device_is_attached(d->dev) ? 1 : 0; ai->next_play_engine = 0; ai->next_rec_engine = 0; ai->busy = 0; ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; ai->iformats = 0; ai->oformats = 0; ai->min_rate = INT_MAX; ai->max_rate = 0; ai->min_channels = INT_MAX; ai->max_channels = 0; /* Gather global information about the device. */ CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); /* * Skip physical channels if we are servicing SNDCTL_AUDIOINFO, * or VCHANs if we are servicing SNDCTL_AUDIOINFO_EX. * * For SNDCTL_AUDIOINFO do not skip the physical channels if * there are no VCHANs. */ if ((ex && (ch->flags & CHN_F_VIRTUAL) != 0) || ((!ex && (ch->flags & CHN_F_VIRTUAL) == 0) && (d->pvchancount > 0 || d->rvchancount > 0))) { CHN_UNLOCK(ch); continue; } if ((ch->flags & CHN_F_BUSY) == 0) { ai->busy |= (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; } ai->caps |= ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); caps = chn_getcaps(ch); minch = INT_MAX; maxch = 0; fmts = 0; for (i = 0; caps->fmtlist[i]; i++) { fmts |= AFMT_ENCODING(caps->fmtlist[i]); minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch); } if (ch->direction == PCMDIR_PLAY) ai->oformats |= fmts; else ai->iformats |= fmts; if (ex || (pcm_getflags(d->dev) & SD_F_BITPERFECT)) { ai->min_rate = min(ai->min_rate, caps->minspeed); ai->max_rate = max(ai->max_rate, caps->maxspeed); } else { ai->min_rate = min(ai->min_rate, feeder_rate_min); ai->max_rate = max(ai->max_rate, feeder_rate_max); } ai->min_channels = min(ai->min_channels, minch); ai->max_channels = max(ai->max_channels, maxch); CHN_UNLOCK(ch); } if (ai->min_rate == INT_MAX) ai->min_rate = 0; if (ai->min_channels == INT_MAX) ai->min_channels = 0; PCM_UNLOCK(d); return (0); } static int dsp_oss_engineinfo_cb(void *data, void *arg) { struct dsp_cdevpriv *priv = data; struct pcm_channel *ch = arg; if (DSP_REGISTERED(priv->sc) && (ch == priv->rdch || ch == priv->wrch)) return (1); return (0); } /** * @brief Handler for SNDCTL_ENGINEINFO * * Gathers information about the audio device's engine specified in ai->dev. * If ai->dev == -1, then this function gathers information about the current * device. If the call comes in on a non-audio device and ai->dev == -1, * return EINVAL. * * This routine is supposed to go practically straight to the hardware, * getting capabilities directly from the sound card driver, side-stepping * the intermediate channel interface. * * @note * Calling threads must not hold any snddev_info or pcm_channel locks. * * @param dev device on which the ioctl was issued * @param ai ioctl request data container * * @retval 0 success * @retval EINVAL ai->dev specifies an invalid device */ int dsp_oss_engineinfo(struct cdev *i_dev, oss_audioinfo *ai) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, nchan, *rates, minch, maxch, unit; /* * If probing the device that received the ioctl, make sure it's a * DSP device. (Users may use this ioctl with /dev/mixer and * /dev/midi.) */ if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) return (EINVAL); ch = NULL; nchan = 0; /* * Search for the requested audio device (channel). Start by * iterating over pcm devices. */ for (unit = 0; pcm_devclass != NULL && unit < devclass_get_maxunit(pcm_devclass); unit++) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry ??? */ /* See the note in function docblock */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); if ((ai->dev == -1 && devfs_foreach_cdevpriv( i_dev, dsp_oss_engineinfo_cb, ch) != 0) || ai->dev == nchan) break; CHN_UNLOCK(ch); ++nchan; } if (ch == NULL) { PCM_UNLOCK(d); continue; } /* * At this point, the following synchronization stuff * has happened: * - a specific PCM device is locked. * - a specific audio channel has been locked, so be * sure to unlock when exiting; */ caps = chn_getcaps(ch); /* * With all handles collected, zero out the user's * container and begin filling in its fields. */ bzero((void *)ai, sizeof(oss_audioinfo)); ai->dev = nchan; strlcpy(ai->name, ch->name, sizeof(ai->name)); if ((ch->flags & CHN_F_BUSY) == 0) ai->busy = 0; else ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; ai->pid = ch->pid; strlcpy(ai->cmd, ch->comm, sizeof(ai->cmd)); /* * These flags stolen from SNDCTL_DSP_GETCAPS handler. * Note, however, that a single channel operates in * only one direction, so PCM_CAP_DUPLEX is out. */ /** * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep * these in pcmchan::caps? */ ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); /* * Collect formats supported @b natively by the * device. Also determine min/max channels. */ minch = INT_MAX; maxch = 0; fmts = 0; for (i = 0; caps->fmtlist[i]; i++) { fmts |= AFMT_ENCODING(caps->fmtlist[i]); minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch); } if (ch->direction == PCMDIR_PLAY) ai->oformats = fmts; else ai->iformats = fmts; /** * @note * @c magic - OSSv4 docs: "Reserved for internal use * by OSS." * * @par * @c card_number - OSSv4 docs: "Number of the sound * card where this device belongs or -1 if this * information is not available. Applications * should normally not use this field for any * purpose." */ ai->card_number = unit; /** * @todo @c song_name - depends first on * SNDCTL_[GS]ETSONG @todo @c label - depends * on SNDCTL_[GS]ETLABEL * @todo @c port_number - routing information? */ ai->port_number = unit; ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; /** * @note * @c legacy_device - OSSv4 docs: "Obsolete." */ ai->legacy_device = unit; snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); ai->enabled = device_is_attached(d->dev) ? 1 : 0; /** * @note * @c flags - OSSv4 docs: "Reserved for future use." * * @note * @c binding - OSSv4 docs: "Reserved for future use." * * @todo @c handle - haven't decided how to generate * this yet; bus, vendor, device IDs? */ if ((ch->flags & CHN_F_EXCLUSIVE) || (pcm_getflags(d->dev) & SD_F_BITPERFECT)) { ai->min_rate = caps->minspeed; ai->max_rate = caps->maxspeed; } else { ai->min_rate = feeder_rate_min; ai->max_rate = feeder_rate_max; } ai->min_channels = minch; ai->max_channels = maxch; ai->nrates = chn_getrates(ch, &rates); if (ai->nrates > OSS_MAX_SAMPLE_RATES) ai->nrates = OSS_MAX_SAMPLE_RATES; for (i = 0; i < ai->nrates; i++) ai->rates[i] = rates[i]; ai->next_play_engine = 0; ai->next_rec_engine = 0; CHN_UNLOCK(ch); PCM_UNLOCK(d); return (0); } /* Exhausted the search -- nothing is locked, so return. */ return (EINVAL); } /** * @brief Assigns a PCM channel to a sync group. * * Sync groups are used to enable audio operations on multiple devices * simultaneously. They may be used with any number of devices and may * span across applications. Devices are added to groups with * the SNDCTL_DSP_SYNCGROUP ioctl, and operations are triggered with the * SNDCTL_DSP_SYNCSTART ioctl. * * If the @c id field of the @c group parameter is set to zero, then a new * sync group is created. Otherwise, wrch and rdch (if set) are added to * the group specified. * * @todo As far as memory allocation, should we assume that things are * okay and allocate with M_WAITOK before acquiring channel locks, * freeing later if not? * * @param wrch output channel associated w/ device (if any) * @param rdch input channel associated w/ device (if any) * @param group Sync group parameters * * @retval 0 success * @retval non-zero error to be propagated upstream */ static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group) { struct pcmchan_syncmember *smrd, *smwr; struct pcmchan_syncgroup *sg; int ret, sg_ids[3]; smrd = NULL; smwr = NULL; sg = NULL; ret = 0; /* * Free_unr() may sleep, so store released syncgroup IDs until after * all locks are released. */ sg_ids[0] = sg_ids[1] = sg_ids[2] = 0; PCM_SG_LOCK(); /* * - Insert channel(s) into group's member list. * - Set CHN_F_NOTRIGGER on channel(s). * - Stop channel(s). */ /* * If device's channels are already mapped to a group, unmap them. */ if (wrch) { CHN_LOCK(wrch); sg_ids[0] = chn_syncdestroy(wrch); } if (rdch) { CHN_LOCK(rdch); sg_ids[1] = chn_syncdestroy(rdch); } /* * Verify that mode matches character device properites. * - Bail if PCM_ENABLE_OUTPUT && wrch == NULL. * - Bail if PCM_ENABLE_INPUT && rdch == NULL. */ if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) || ((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) { ret = EINVAL; goto out; } /* * An id of zero indicates the user wants to create a new * syncgroup. */ if (group->id == 0) { sg = malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); if (sg != NULL) { SLIST_INIT(&sg->members); sg->id = alloc_unr(pcmsg_unrhdr); group->id = sg->id; SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link); } else ret = ENOMEM; } else { SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { if (sg->id == group->id) break; } if (sg == NULL) ret = EINVAL; } /* Couldn't create or find a syncgroup. Fail. */ if (sg == NULL) goto out; /* * Allocate a syncmember, assign it and a channel together, and * insert into syncgroup. */ if (group->mode & PCM_ENABLE_INPUT) { smrd = malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); if (smrd == NULL) { ret = ENOMEM; goto out; } SLIST_INSERT_HEAD(&sg->members, smrd, link); smrd->parent = sg; smrd->ch = rdch; chn_abort(rdch); rdch->flags |= CHN_F_NOTRIGGER; rdch->sm = smrd; } if (group->mode & PCM_ENABLE_OUTPUT) { smwr = malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); if (smwr == NULL) { ret = ENOMEM; goto out; } SLIST_INSERT_HEAD(&sg->members, smwr, link); smwr->parent = sg; smwr->ch = wrch; chn_abort(wrch); wrch->flags |= CHN_F_NOTRIGGER; wrch->sm = smwr; } out: if (ret != 0) { if (smrd != NULL) free(smrd, M_DEVBUF); if ((sg != NULL) && SLIST_EMPTY(&sg->members)) { sg_ids[2] = sg->id; SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); free(sg, M_DEVBUF); } if (wrch) wrch->sm = NULL; if (rdch) rdch->sm = NULL; } if (wrch) CHN_UNLOCK(wrch); if (rdch) CHN_UNLOCK(rdch); PCM_SG_UNLOCK(); if (sg_ids[0]) free_unr(pcmsg_unrhdr, sg_ids[0]); if (sg_ids[1]) free_unr(pcmsg_unrhdr, sg_ids[1]); if (sg_ids[2]) free_unr(pcmsg_unrhdr, sg_ids[2]); return (ret); } /** * @brief Launch a sync group into action * * Sync groups are established via SNDCTL_DSP_SYNCGROUP. This function * iterates over all members, triggering them along the way. * * @note Caller must not hold any channel locks. * * @param sg_id sync group identifier * * @retval 0 success * @retval non-zero error worthy of propagating upstream to user */ static int dsp_oss_syncstart(int sg_id) { struct pcmchan_syncmember *sm, *sm_tmp; struct pcmchan_syncgroup *sg; struct pcm_channel *c; int ret, needlocks; /* Get the synclists lock */ PCM_SG_LOCK(); do { ret = 0; needlocks = 0; /* Search for syncgroup by ID */ SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { if (sg->id == sg_id) break; } /* Return EINVAL if not found */ if (sg == NULL) { ret = EINVAL; break; } /* Any removals resulting in an empty group should've handled this */ KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup")); /* * Attempt to lock all member channels - if any are already * locked, unlock those acquired, sleep for a bit, and try * again. */ SLIST_FOREACH(sm, &sg->members, link) { if (CHN_TRYLOCK(sm->ch) == 0) { int timo = hz * 5/1000; if (timo < 1) timo = 1; /* Release all locked channels so far, retry */ SLIST_FOREACH(sm_tmp, &sg->members, link) { /* sm is the member already locked */ if (sm == sm_tmp) break; CHN_UNLOCK(sm_tmp->ch); } /** @todo Is PRIBIO correct/ */ ret = msleep(sm, &snd_pcm_syncgroups_mtx, PRIBIO | PCATCH, "pcmsg", timo); if (ret == EINTR || ret == ERESTART) break; needlocks = 1; ret = 0; /* Assumes ret == EAGAIN... */ } } } while (needlocks && ret == 0); /* Proceed only if no errors encountered. */ if (ret == 0) { /* Launch channels */ while ((sm = SLIST_FIRST(&sg->members)) != NULL) { SLIST_REMOVE_HEAD(&sg->members, link); c = sm->ch; c->sm = NULL; chn_start(c, 1); c->flags &= ~CHN_F_NOTRIGGER; CHN_UNLOCK(c); free(sm, M_DEVBUF); } SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); free(sg, M_DEVBUF); } PCM_SG_UNLOCK(); /* * Free_unr() may sleep, so be sure to give up the syncgroup lock * first. */ if (ret == 0) free_unr(pcmsg_unrhdr, sg_id); return (ret); } /** * @brief Handler for SNDCTL_DSP_POLICY * * The SNDCTL_DSP_POLICY ioctl is a simpler interface to control fragment * size and count like with SNDCTL_DSP_SETFRAGMENT. Instead of the user * specifying those two parameters, s/he simply selects a number from 0..10 * which corresponds to a buffer size. Smaller numbers request smaller * buffers with lower latencies (at greater overhead from more frequent * interrupts), while greater numbers behave in the opposite manner. * * The 4Front spec states that a value of 5 should be the default. However, * this implementation deviates slightly by using a linear scale without * consulting drivers. I.e., even though drivers may have different default * buffer sizes, a policy argument of 5 will have the same result across * all drivers. * * See http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html for * more information. * * @todo When SNDCTL_DSP_COOKEDMODE is supported, it'll be necessary to * work with hardware drivers directly. * * @note PCM channel arguments must not be locked by caller. * * @param wrch Pointer to opened playback channel (optional; may be NULL) * @param rdch " recording channel (optional; may be NULL) * @param policy Integer from [0:10] * * @retval 0 constant (for now) */ static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) { int ret; if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) return (EIO); /* Default: success */ ret = 0; if (rdch) { CHN_LOCK(rdch); ret = chn_setlatency(rdch, policy); CHN_UNLOCK(rdch); } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setlatency(wrch, policy); CHN_UNLOCK(wrch); } if (ret) ret = EIO; return (ret); } /** * @brief Enable or disable "cooked" mode * * This is a handler for @c SNDCTL_DSP_COOKEDMODE. When in cooked mode, which * is the default, the sound system handles rate and format conversions * automatically (ex: user writing 11025Hz/8 bit/unsigned but card only * operates with 44100Hz/16bit/signed samples). * * Disabling cooked mode is intended for applications wanting to mmap() * a sound card's buffer space directly, bypassing the FreeBSD 2-stage * feeder architecture, presumably to gain as much control over audio * hardware as possible. * * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_COOKEDMODE.html * for more details. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param enabled 0 = raw mode, 1 = cooked mode * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled) { /* * XXX I just don't get it. Why don't they call it * "BITPERFECT" ~ SNDCTL_DSP_BITPERFECT !?!?. * This is just plain so confusing, incoherent, * . */ if (!(enabled == 1 || enabled == 0)) return (EINVAL); /* * I won't give in. I'm inverting its logic here and now. * Brag all you want, but "BITPERFECT" should be the better * term here. */ enabled ^= 0x00000001; if (wrch != NULL) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_BITPERFECT; wrch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_BITPERFECT; rdch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(rdch); } return (0); } /** * @brief Retrieve channel interleaving order * * This is the handler for @c SNDCTL_DSP_GET_CHNORDER. * * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_GET_CHNORDER.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_DSP_GET_CHNORDER. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param map channel map (result will be stored there) * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) { struct pcm_channel *ch; int ret; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getorder(ch, map); CHN_UNLOCK(ch); } else ret = EINVAL; return (ret); } /** * @brief Specify channel interleaving order * * This is the handler for @c SNDCTL_DSP_SET_CHNORDER. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support @c SNDCTL_DSP_SET_CHNORDER. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param map channel map * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) { int ret; ret = 0; if (wrch != NULL) { CHN_LOCK(wrch); ret = chn_oss_setorder(wrch, map); CHN_UNLOCK(wrch); } if (ret == 0 && rdch != NULL) { CHN_LOCK(rdch); ret = chn_oss_setorder(rdch, map); CHN_UNLOCK(rdch); } return (ret); } static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask) { struct pcm_channel *ch; uint32_t chnmask; int ret; chnmask = 0; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getmask(ch, &chnmask); CHN_UNLOCK(ch); } else ret = EINVAL; if (ret == 0) *mask = chnmask; return (ret); } #ifdef OSSV4_EXPERIMENT /** * @brief Retrieve an audio device's label * * This is a handler for the @c SNDCTL_GETLABEL ioctl. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html * for more details. * * From Hannu@4Front: "For example ossxmix (just like some HW mixer * consoles) can show variable "labels" for certain controls. By default * the application name (say quake) is shown as the label but * applications may change the labels themselves." * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support @c SNDCTL_GETLABEL. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param label label gets copied here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) { return (EINVAL); } /** * @brief Specify an audio device's label * * This is a handler for the @c SNDCTL_SETLABEL ioctl. Please see the * comments for @c dsp_oss_getlabel immediately above. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETLABEL. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param label label gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) { return (EINVAL); } /** * @brief Retrieve name of currently played song * * This is a handler for the @c SNDCTL_GETSONG ioctl. Audio players could * tell the system the name of the currently playing song, which would be * visible in @c /dev/sndstat. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETSONG.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_GETSONG. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param song song name gets copied here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) { return (EINVAL); } /** * @brief Retrieve name of currently played song * * This is a handler for the @c SNDCTL_SETSONG ioctl. Audio players could * tell the system the name of the currently playing song, which would be * visible in @c /dev/sndstat. * * See @c http://manuals.opensound.com/developer/SNDCTL_SETSONG.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETSONG. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param song song name gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) { return (EINVAL); } /** * @brief Rename a device * * This is a handler for the @c SNDCTL_SETNAME ioctl. * * See @c http://manuals.opensound.com/developer/SNDCTL_SETNAME.html for * more details. * * From Hannu@4Front: "This call is used to change the device name * reported in /dev/sndstat and ossinfo. So instead of using some generic * 'OSS loopback audio (MIDI) driver' the device may be given a meaningfull * name depending on the current context (for example 'OSS virtual wave table * synth' or 'VoIP link to London')." * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETNAME. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param name new device name gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name) { return (EINVAL); } #endif /* !OSSV4_EXPERIMENT */ diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index 2d57852e036d..c0934dde7718 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -1,800 +1,746 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * Copyright (c) 1997 Luigi Rizzo * All rights reserved. * Copyright (c) 2024 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "feeder_if.h" devclass_t pcm_devclass; int snd_unit = -1; static int snd_unit_auto = -1; SYSCTL_INT(_hw_snd, OID_AUTO, default_auto, CTLFLAG_RWTUN, &snd_unit_auto, 0, "assign default unit to a newly attached device"); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Sound driver"); static void pcm_sysinit(device_t); /** * @brief Unit number allocator for syncgroup IDs */ struct unrhdr *pcmsg_unrhdr = NULL; void * snd_mtxcreate(const char *desc, const char *type) { struct mtx *m; m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); mtx_init(m, desc, type, MTX_DEF); return m; } void snd_mtxfree(void *m) { struct mtx *mtx = m; mtx_destroy(mtx); free(mtx, M_DEVBUF); } void snd_mtxassert(void *m) { #ifdef INVARIANTS struct mtx *mtx = m; mtx_assert(mtx, MA_OWNED); #endif } int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) { struct snddev_info *d; flags &= INTR_MPSAFE; flags |= INTR_TYPE_AV; d = device_get_softc(dev); if (d != NULL && (flags & INTR_MPSAFE)) d->flags |= SD_F_MPSAFE; return bus_setup_intr(dev, res, flags, NULL, hand, param, cookiep); } -int -pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, - pid_t pid, char *comm) -{ - struct pcm_channel *c; - int err, vchancount; - bool retry; - - KASSERT(d != NULL && ch != NULL && - (direction == PCMDIR_PLAY || direction == PCMDIR_REC), - ("%s(): invalid d=%p ch=%p direction=%d pid=%d", - __func__, d, ch, direction, pid)); - PCM_BUSYASSERT(d); - - *ch = NULL; - vchancount = (direction == PCMDIR_PLAY) ? d->pvchancount : - d->rvchancount; - retry = false; - -retry_chnalloc: - /* Scan for a free channel. */ - CHN_FOREACH(c, d, channels.pcm) { - CHN_LOCK(c); - if (c->direction != direction) { - CHN_UNLOCK(c); - continue; - } - if (!(c->flags & CHN_F_BUSY)) { - c->flags |= CHN_F_BUSY; - c->pid = pid; - strlcpy(c->comm, (comm != NULL) ? comm : - CHN_COMM_UNKNOWN, sizeof(c->comm)); - *ch = c; - - return (0); - } - CHN_UNLOCK(c); - } - /* Maybe next time... */ - if (retry) - return (EBUSY); - - /* No channel available. We also cannot create more VCHANs. */ - if (!(vchancount > 0 && vchancount < snd_maxautovchans)) - return (ENOTSUP); - - /* Increase the VCHAN count and try to get the new channel. */ - err = vchan_setnew(d, direction, vchancount + 1); - if (err == 0) { - retry = true; - goto retry_chnalloc; - } - - return (err); -} - static int sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int error, unit; unit = snd_unit; error = sysctl_handle_int(oidp, &unit, 0, req); if (error == 0 && req->newptr != NULL) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d) || CHN_EMPTY(d, channels.pcm)) return EINVAL; snd_unit = unit; snd_unit_auto = 0; } return (error); } /* XXX: do we need a way to let the user change the default unit? */ SYSCTL_PROC(_hw_snd, OID_AUTO, default_unit, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_ANYBODY | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_default_unit, "I", "default sound device"); int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo) { struct snddev_info *d = device_get_softc(dev); struct pcm_channel *ch; PCM_BUSYASSERT(d); PCM_LOCK(d); ch = chn_init(d, NULL, cls, dir, devinfo); if (!ch) { device_printf(d->dev, "chn_init(%s, %d, %p) failed\n", cls->name, dir, devinfo); PCM_UNLOCK(d); return (ENODEV); } PCM_UNLOCK(d); return (0); } static void pcm_killchans(struct snddev_info *d) { struct pcm_channel *ch; bool again; PCM_BUSYASSERT(d); KASSERT(!PCM_REGISTERED(d), ("%s(): still registered\n", __func__)); for (;;) { again = false; /* Make sure all channels are stopped. */ CHN_FOREACH(ch, d, channels.pcm) { CHN_LOCK(ch); if (ch->inprog == 0 && ch->sleeping == 0 && CHN_STOPPED(ch)) { CHN_UNLOCK(ch); continue; } chn_shutdown(ch); if (ch->direction == PCMDIR_PLAY) chn_flush(ch); else chn_abort(ch); CHN_UNLOCK(ch); again = true; } /* * Some channels are still active. Sleep for a bit and try * again. */ if (again) pause_sbt("pcmkillchans", mstosbt(5), 0, 0); else break; } /* All channels are finally dead. */ while (!CHN_EMPTY(d, channels.pcm)) { ch = CHN_FIRST(d, channels.pcm); chn_kill(ch); } if (d->p_unr != NULL) delete_unrhdr(d->p_unr); if (d->vp_unr != NULL) delete_unrhdr(d->vp_unr); if (d->r_unr != NULL) delete_unrhdr(d->r_unr); if (d->vr_unr != NULL) delete_unrhdr(d->vr_unr); } static int pcm_best_unit(int old) { struct snddev_info *d; int i, best, bestprio, prio; best = -1; bestprio = -100; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; prio = 0; if (d->playcount == 0) prio -= 10; if (d->reccount == 0) prio -= 2; if (prio > bestprio || (prio == bestprio && i == old)) { best = i; bestprio = prio; } } return (best); } uint32_t pcm_getflags(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->flags; } void pcm_setflags(device_t dev, uint32_t val) { struct snddev_info *d = device_get_softc(dev); d->flags = val; } void * pcm_getdevinfo(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->devinfo; } unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz) { struct snddev_info *d = device_get_softc(dev); int sz, x; sz = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) { x = sz; RANGE(sz, minbufsz, maxbufsz); if (x != sz) device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, minbufsz, maxbufsz, sz); x = minbufsz; while (x < sz) x <<= 1; if (x > sz) x >>= 1; if (x != sz) { device_printf(dev, "'buffersize=%d' hint is not a power of 2, using %d\n", sz, x); sz = x; } } else { sz = deflt; } d->bufsz = sz; return sz; } static int sysctl_dev_pcm_bitperfect(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int err, val; d = oidp->oid_arg1; if (!PCM_REGISTERED(d)) return (ENODEV); PCM_LOCK(d); PCM_WAIT(d); val = (d->flags & SD_F_BITPERFECT) ? 1 : 0; PCM_ACQUIRE(d); PCM_UNLOCK(d); err = sysctl_handle_int(oidp, &val, 0, req); if (err == 0 && req->newptr != NULL) { if (!(val == 0 || val == 1)) { PCM_RELEASE_QUICK(d); return (EINVAL); } PCM_LOCK(d); d->flags &= ~SD_F_BITPERFECT; d->flags |= (val != 0) ? SD_F_BITPERFECT : 0; PCM_RELEASE(d); PCM_UNLOCK(d); } else PCM_RELEASE_QUICK(d); return (err); } static u_int8_t pcm_mode_init(struct snddev_info *d) { u_int8_t mode = 0; if (d->playcount > 0) mode |= PCM_MODE_PLAY; if (d->reccount > 0) mode |= PCM_MODE_REC; if (d->mixer_dev != NULL) mode |= PCM_MODE_MIXER; return (mode); } static void pcm_sysinit(device_t dev) { struct snddev_info *d = device_get_softc(dev); u_int8_t mode; mode = pcm_mode_init(d); sysctl_ctx_init(&d->play_sysctl_ctx); d->play_sysctl_tree = SYSCTL_ADD_NODE(&d->play_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "play", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "playback channels node"); sysctl_ctx_init(&d->rec_sysctl_ctx); d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "recording channels node"); /* XXX: a user should be able to set this with a control tool, the sysadmin then needs min+max sysctls for this */ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "bitperfect", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, d, sizeof(d), sysctl_dev_pcm_bitperfect, "I", "bit-perfect playback/recording (0=disable, 1=enable)"); SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mode", CTLFLAG_RD, NULL, mode, "mode (1=mixer, 2=play, 4=rec. The values are OR'ed if more than " "one mode is supported)"); if (d->flags & SD_F_AUTOVCHAN) vchan_initsys(dev); if (d->flags & SD_F_EQ) feeder_eq_initsys(dev); } /* * Basic initialization so that drivers can use pcm_addchan() before * pcm_register(). */ void pcm_init(device_t dev, void *devinfo) { struct snddev_info *d; int i; d = device_get_softc(dev); d->dev = dev; d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); cv_init(&d->cv, device_get_nameunit(dev)); PCM_ACQUIRE_QUICK(d); i = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "vpc", &i) != 0 || i != 0) d->flags |= SD_F_VPC; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "bitperfect", &i) == 0 && i != 0) d->flags |= SD_F_BITPERFECT; d->devinfo = devinfo; d->reccount = 0; d->playcount = 0; d->pvchancount = 0; d->rvchancount = 0; d->pvchanrate = 0; d->pvchanformat = 0; d->rvchanrate = 0; d->rvchanformat = 0; d->p_unr = new_unrhdr(0, INT_MAX, NULL); d->vp_unr = new_unrhdr(0, INT_MAX, NULL); d->r_unr = new_unrhdr(0, INT_MAX, NULL); d->vr_unr = new_unrhdr(0, INT_MAX, NULL); CHN_INIT(d, channels.pcm); CHN_INIT(d, channels.pcm.busy); CHN_INIT(d, channels.pcm.opened); + CHN_INIT(d, channels.pcm.primary); } int pcm_register(device_t dev, char *str) { struct snddev_info *d = device_get_softc(dev); /* should only be called once */ if (d->flags & SD_F_REGISTERED) return (EINVAL); PCM_BUSYASSERT(d); if (d->playcount == 0 || d->reccount == 0) d->flags |= SD_F_SIMPLEX; if (d->playcount > 0 || d->reccount > 0) d->flags |= SD_F_AUTOVCHAN; - vchan_setmaxauto(d, snd_maxautovchans); + if (d->playcount > 0) + d->flags |= SD_F_PVCHANS; + if (d->reccount > 0) + d->flags |= SD_F_RVCHANS; strlcpy(d->status, str, SND_STATUSLEN); sndstat_register(dev, d->status); PCM_LOCK(d); /* Done, we're ready.. */ d->flags |= SD_F_REGISTERED; PCM_RELEASE(d); PCM_UNLOCK(d); /* * Create all sysctls once SD_F_REGISTERED is set else * tunable sysctls won't work: */ pcm_sysinit(dev); if (snd_unit_auto < 0) snd_unit_auto = (snd_unit < 0) ? 1 : 0; if (snd_unit < 0 || snd_unit_auto > 1) snd_unit = device_get_unit(dev); else if (snd_unit_auto == 1) snd_unit = pcm_best_unit(snd_unit); return (dsp_make_dev(dev)); } int pcm_unregister(device_t dev) { struct snddev_info *d; d = device_get_softc(dev); if (!PCM_ALIVE(d)) { device_printf(dev, "unregister: device not configured\n"); return (0); } PCM_LOCK(d); PCM_WAIT(d); d->flags &= ~SD_F_REGISTERED; PCM_ACQUIRE(d); PCM_UNLOCK(d); pcm_killchans(d); PCM_RELEASE_QUICK(d); if (d->play_sysctl_tree != NULL) { sysctl_ctx_free(&d->play_sysctl_ctx); d->play_sysctl_tree = NULL; } if (d->rec_sysctl_tree != NULL) { sysctl_ctx_free(&d->rec_sysctl_ctx); d->rec_sysctl_tree = NULL; } sndstat_unregister(dev); mixer_uninit(dev); dsp_destroy_dev(dev); cv_destroy(&d->cv); snd_mtxfree(d->lock); if (snd_unit == device_get_unit(dev)) { snd_unit = pcm_best_unit(-1); if (snd_unit_auto == 0) snd_unit_auto = 1; } return (0); } /************************************************************************/ /** * @brief Handle OSSv4 SNDCTL_SYSINFO ioctl. * * @param si Pointer to oss_sysinfo struct where information about the * sound subsystem will be written/copied. * * This routine returns information about the sound system, such as the * current OSS version, number of audio, MIDI, and mixer drivers, etc. * Also includes a bitmask showing which of the above types of devices * are open (busy). * * @note * Calling threads must not hold any snddev_info or pcm_channel locks. * * @author Ryan Beasley */ void sound_oss_sysinfo(oss_sysinfo *si) { static char si_product[] = "FreeBSD native OSS ABI"; static char si_version[] = __XSTRING(__FreeBSD_version); static char si_license[] = "BSD"; static int intnbits = sizeof(int) * 8; /* Better suited as macro? Must pester a C guru. */ struct snddev_info *d; struct pcm_channel *c; int j; size_t i; strlcpy(si->product, si_product, sizeof(si->product)); strlcpy(si->version, si_version, sizeof(si->version)); si->versionnum = SOUND_VERSION; strlcpy(si->license, si_license, sizeof(si->license)); /* * Iterate over PCM devices and their channels, gathering up data * for the numaudioengines and openedaudio fields. */ si->numaudioengines = 0; bzero((void *)&si->openedaudio, sizeof(si->openedaudio)); j = 0; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry ??? */ /* See note in function's docblock */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); si->numaudioengines += PCM_CHANCOUNT(d); CHN_FOREACH(c, d, channels.pcm) { CHN_UNLOCKASSERT(c); CHN_LOCK(c); if (c->flags & CHN_F_BUSY) si->openedaudio[j / intnbits] |= (1 << (j % intnbits)); CHN_UNLOCK(c); j++; } PCM_UNLOCK(d); } si->numsynths = 0; /* OSSv4 docs: this field is obsolete */ /** * @todo Collect num{midis,timers}. * * Need access to sound/midi/midi.c::midistat_lock in order * to safely touch midi_devices and get a head count of, well, * MIDI devices. midistat_lock is a global static (i.e., local to * midi.c), but midi_devices is a regular global; should the mutex * be publicized, or is there another way to get this information? * * NB: MIDI/sequencer stuff is currently on hold. */ si->nummidis = 0; si->numtimers = 0; /* * Set this to the maximum unit number so that applications will not * break if they try to loop through all mixers and some of them are * not available. */ si->nummixers = devclass_get_maxunit(pcm_devclass); si->numcards = devclass_get_maxunit(pcm_devclass); si->numaudios = devclass_get_maxunit(pcm_devclass); /* OSSv4 docs: Intended only for test apps; API doesn't really have much of a concept of cards. Shouldn't be used by applications. */ /** * @todo Fill in "busy devices" fields. * * si->openedmidi = " MIDI devices */ bzero((void *)&si->openedmidi, sizeof(si->openedmidi)); /* * Si->filler is a reserved array, but according to docs each * element should be set to -1. */ for (i = 0; i < nitems(si->filler); i++) si->filler[i] = -1; } int sound_oss_card_info(oss_card_info *si) { struct snddev_info *d; int i; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (i != si->card) continue; if (!PCM_REGISTERED(d)) { snprintf(si->shortname, sizeof(si->shortname), "pcm%d (n/a)", i); strlcpy(si->longname, "Device unavailable", sizeof(si->longname)); si->hw_info[0] = '\0'; si->intr_count = si->ack_count = 0; } else { PCM_UNLOCKASSERT(d); PCM_LOCK(d); strlcpy(si->shortname, device_get_nameunit(d->dev), sizeof(si->shortname)); strlcpy(si->longname, device_get_desc(d->dev), sizeof(si->longname)); strlcpy(si->hw_info, d->status, sizeof(si->hw_info)); si->intr_count = si->ack_count = 0; PCM_UNLOCK(d); } return (0); } return (ENXIO); } /************************************************************************/ static void sound_global_init(void) { if (snd_verbose < 0 || snd_verbose > 4) snd_verbose = 1; if (snd_unit < 0) snd_unit = -1; - if (snd_maxautovchans < 0 || - snd_maxautovchans > SND_MAXVCHANS) - snd_maxautovchans = 0; + snd_vchans_enable = true; if (chn_latency < CHN_LATENCY_MIN || chn_latency > CHN_LATENCY_MAX) chn_latency = CHN_LATENCY_DEFAULT; if (chn_latency_profile < CHN_LATENCY_PROFILE_MIN || chn_latency_profile > CHN_LATENCY_PROFILE_MAX) chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; if (feeder_rate_min < FEEDRATE_MIN || feeder_rate_max < FEEDRATE_MIN || feeder_rate_min > FEEDRATE_MAX || feeder_rate_max > FEEDRATE_MAX || !(feeder_rate_min < feeder_rate_max)) { feeder_rate_min = FEEDRATE_RATEMIN; feeder_rate_max = FEEDRATE_RATEMAX; } if (feeder_rate_round < FEEDRATE_ROUNDHZ_MIN || feeder_rate_round > FEEDRATE_ROUNDHZ_MAX) feeder_rate_round = FEEDRATE_ROUNDHZ; if (bootverbose) - printf("%s: snd_unit=%d snd_maxautovchans=%d " + printf("%s: snd_unit=%d snd_vchans_enable=%d " "latency=%d " "feeder_rate_min=%d feeder_rate_max=%d " "feeder_rate_round=%d\n", - __func__, snd_unit, snd_maxautovchans, + __func__, snd_unit, snd_vchans_enable, chn_latency, feeder_rate_min, feeder_rate_max, feeder_rate_round); } static int sound_modevent(module_t mod, int type, void *data) { int ret; ret = 0; switch (type) { case MOD_LOAD: pcm_devclass = devclass_create("pcm"); pcmsg_unrhdr = new_unrhdr(1, INT_MAX, NULL); sound_global_init(); break; case MOD_UNLOAD: if (pcmsg_unrhdr != NULL) { delete_unrhdr(pcmsg_unrhdr); pcmsg_unrhdr = NULL; } break; case MOD_SHUTDOWN: break; default: ret = ENOTSUP; } return ret; } DEV_MODULE(sound, sound_modevent, NULL); MODULE_VERSION(sound, SOUND_MODVER); diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 019fe67ac892..0452a58dfcbf 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -1,511 +1,514 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * Copyright (c) 1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #include #include #include #define PCM_SOFTC_SIZE (sizeof(struct snddev_info)) #define SND_STATUSLEN 64 #define SOUND_MODVER 5 #define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER SOUND_MODVER -#define SND_MAXVCHANS 256 - #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 #define SD_F_BUSY 0x00000008 #define SD_F_MPSAFE 0x00000010 #define SD_F_REGISTERED 0x00000020 #define SD_F_BITPERFECT 0x00000040 #define SD_F_VPC 0x00000080 /* volume-per-channel */ #define SD_F_EQ 0x00000100 /* EQ */ #define SD_F_EQ_ENABLED 0x00000200 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00000400 /* EQ bypassed */ #define SD_F_EQ_PC 0x00000800 /* EQ per-channel */ +#define SD_F_PVCHANS 0x00001000 /* Playback vchans enabled */ +#define SD_F_RVCHANS 0x00002000 /* Recording vchans enabled */ #define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED) #define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \ SD_F_EQ_BYPASSED | SD_F_EQ_PC) #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_BITS "\020" \ "\001SIMPLEX" \ "\002AUTOVCHAN" \ "\003SOFTPCMVOL" \ "\004BUSY" \ "\005MPSAFE" \ "\006REGISTERED" \ "\007BITPERFECT" \ "\010VPC" \ "\011EQ" \ "\012EQ_ENABLED" \ "\013EQ_BYPASSED" \ "\014EQ_PC" \ + "\015PVCHANS" \ + "\016RVCHANS" \ "\035PRIO_RD" \ "\036PRIO_WR" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && ((x)->flags & SD_F_REGISTERED)) +#define PCM_MAXCHANS 10000 #define PCM_CHANCOUNT(d) \ (d->playcount + d->pvchancount + d->reccount + d->rvchancount) /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) enum { SND_DEV_CTL = 0, /* Control port /dev/mixer */ SND_DEV_SEQ, /* Sequencer /dev/sequencer */ SND_DEV_MIDIN, /* Raw midi access */ SND_DEV_DSP, /* Digitized voice /dev/dsp */ SND_DEV_STATUS, /* /dev/sndstat */ }; #define DSP_DEFAULT_SPEED 8000 extern int snd_unit; extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); -int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, - pid_t pid, char *comm); - int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); void pcm_init(device_t dev, void *devinfo); int pcm_register(device_t dev, char *str); int pcm_unregister(device_t dev); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep); void *snd_mtxcreate(const char *desc, const char *type); void snd_mtxfree(void *m); void snd_mtxassert(void *m); #define snd_mtxlock(m) mtx_lock(m) #define snd_mtxunlock(m) mtx_unlock(m) int sndstat_register(device_t dev, char *str); int sndstat_unregister(device_t dev); /* These are the function codes assigned to the children of sound cards. */ enum { SCF_PCM, SCF_MIDI, SCF_SYNTH, }; /* * This is the device information struct, used by a bridge device to pass the * device function code to the children. */ struct sndcard_func { int func; /* The function code. */ void *varinfo; /* Bridge-specific information. */ }; /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * we also have to do this now makedev() has gone away. */ struct snddev_info { struct { struct { SLIST_HEAD(, pcm_channel) head; struct { SLIST_HEAD(, pcm_channel) head; } busy; struct { SLIST_HEAD(, pcm_channel) head; } opened; + struct { + SLIST_HEAD(, pcm_channel) head; + } primary; } pcm; } channels; unsigned playcount, reccount, pvchancount, rvchancount; unsigned flags; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; struct cdev *dsp_dev; uint32_t pvchanrate, pvchanformat; uint32_t rvchanrate, rvchanformat; int32_t eqpreamp; struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; struct cv cv; struct unrhdr *p_unr; struct unrhdr *vp_unr; struct unrhdr *r_unr; struct unrhdr *vr_unr; }; void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); #define PCM_MODE_MIXER 0x01 #define PCM_MODE_PLAY 0x02 #define PCM_MODE_REC 0x04 #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) #define PCM_TRYLOCK(d) mtx_trylock((d)->lock) #define PCM_LOCKASSERT(d) mtx_assert((d)->lock, MA_OWNED) #define PCM_UNLOCKASSERT(d) mtx_assert((d)->lock, MA_NOTOWNED) /* * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these * with PCM_LOCK/UNLOCK() sequence, or I'll come to gnaw upon you! */ #ifdef SND_DIAGNOSTIC #define PCM_WAIT(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM WAIT] Mutex not owned!", \ __func__, __LINE__); \ while ((x)->flags & SD_F_BUSY) { \ if (snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM WAIT] calling cv_wait().\n", \ __func__, __LINE__); \ cv_wait(&(x)->cv, (x)->lock); \ } \ } while (0) #define PCM_ACQUIRE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) \ panic("%s(%d): [PCM ACQUIRE] " \ "Trying to acquire BUSY cv!", __func__, __LINE__); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) { \ (x)->flags &= ~SD_F_BUSY; \ cv_broadcast(&(x)->cv); \ } else \ panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) do { \ if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ panic("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", __func__, __LINE__, x); \ } while (0) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT ENTER] Giant owned!\n", \ __func__, __LINE__); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ __func__, __LINE__); \ if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_MPSAFE) { \ if (_pcm_giant == 1) \ panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT EXIT] Giant owned!\n", \ __func__, __LINE__); \ } \ if (_pcm_giant != 0) { \ if (mtx_owned(&Giant) == 0) \ panic("%s(%d): [GIANT EXIT] Giant not owned!", \ __func__, __LINE__); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #else /* !SND_DIAGNOSTIC */ #define PCM_WAIT(x) do { \ PCM_LOCKASSERT(x); \ while ((x)->flags & SD_F_BUSY) \ cv_wait(&(x)->cv, (x)->lock); \ } while (0) #define PCM_ACQUIRE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT(!((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ __func__, __LINE__)); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT((x)->flags & SD_F_BUSY, \ ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__)); \ (x)->flags &= ~SD_F_BUSY; \ cv_broadcast(&(x)->cv); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ ((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", \ __func__, __LINE__, x)) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ PCM_UNLOCKASSERT(x); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ PCM_UNLOCKASSERT(x); \ KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__)); \ KASSERT(!((x)->flags & SD_F_MPSAFE) || \ (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__)); \ if (_pcm_giant != 0) { \ mtx_assert(&Giant, MA_OWNED); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #endif /* SND_DIAGNOSTIC */ #define PCM_GIANT_LEAVE(x) \ PCM_GIANT_EXIT(x); \ } while (0) #endif /* _KERNEL */ /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_G711 (AFMT_MU_LAW | AFMT_A_LAW) #define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8) #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ AFMT_S16_BE | AFMT_U16_BE) #define AFMT_CONVERTIBLE (AFMT_8BIT | AFMT_16BIT | AFMT_24BIT | \ AFMT_32BIT) /* Supported vchan mixing formats */ #define AFMT_VCHAN (AFMT_CONVERTIBLE & ~AFMT_G711) #define AFMT_PASSTHROUGH AFMT_AC3 #define AFMT_PASSTHROUGH_RATE 48000 #define AFMT_PASSTHROUGH_CHANNEL 2 #define AFMT_PASSTHROUGH_EXTCHANNEL 0 /* * We're simply using unused, contiguous bits from various AFMT_ definitions. * ~(0xb00ff7ff) */ #define AFMT_ENCODING_MASK 0xf00fffff #define AFMT_CHANNEL_MASK 0x07f00000 #define AFMT_CHANNEL_SHIFT 20 #define AFMT_CHANNEL_MAX 0x7f #define AFMT_EXTCHANNEL_MASK 0x08000000 #define AFMT_EXTCHANNEL_SHIFT 27 #define AFMT_EXTCHANNEL_MAX 1 #define AFMT_ENCODING(v) ((v) & AFMT_ENCODING_MASK) #define AFMT_EXTCHANNEL(v) (((v) & AFMT_EXTCHANNEL_MASK) >> \ AFMT_EXTCHANNEL_SHIFT) #define AFMT_CHANNEL(v) (((v) & AFMT_CHANNEL_MASK) >> \ AFMT_CHANNEL_SHIFT) #define AFMT_BIT(v) (((v) & AFMT_32BIT) ? 32 : \ (((v) & AFMT_24BIT) ? 24 : \ ((((v) & AFMT_16BIT) || \ ((v) & AFMT_PASSTHROUGH)) ? 16 : 8))) #define AFMT_BPS(v) (AFMT_BIT(v) >> 3) #define AFMT_ALIGN(v) (AFMT_BPS(v) * AFMT_CHANNEL(v)) #define SND_FORMAT(f, c, e) (AFMT_ENCODING(f) | \ (((c) << AFMT_CHANNEL_SHIFT) & \ AFMT_CHANNEL_MASK) | \ (((e) << AFMT_EXTCHANNEL_SHIFT) & \ AFMT_EXTCHANNEL_MASK)) #define AFMT_U8_NE AFMT_U8 #define AFMT_S8_NE AFMT_S8 #define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) #endif /* _OS_H_ */ diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index b0caec3acfec..297120199fe7 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -1,1102 +1,983 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006-2009 Ariff Abdullah * Copyright (c) 2001 Cameron Grant * All rights reserved. * Copyright (c) 2024 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* Almost entirely rewritten to add multi-format/channels mixing support. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include /* * [ac3 , dts , linear , 0, linear, 0] */ #define FMTLIST_MAX 6 #define FMTLIST_OFFSET 4 #define DIGFMTS_MAX 2 #ifdef SND_DEBUG static int snd_passthrough_verbose = 0; SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RWTUN, &snd_passthrough_verbose, 0, "passthrough verbosity"); #endif struct vchan_info { struct pcm_channel *channel; struct pcmchan_caps caps; uint32_t fmtlist[FMTLIST_MAX]; int trigger; }; -int snd_maxautovchans = 16; +bool snd_vchans_enable = true; static void * vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct vchan_info *info; struct pcm_channel *p; uint32_t i, j, *fmtlist; KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, ("vchan_init: bad direction")); KASSERT(c != NULL && c->parentchannel != NULL, ("vchan_init: bad channels")); info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO); info->channel = c; info->trigger = PCMTRIG_STOP; p = c->parentchannel; CHN_LOCK(p); fmtlist = chn_getcaps(p)->fmtlist; for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) { if (fmtlist[i] & AFMT_PASSTHROUGH) info->fmtlist[j++] = fmtlist[i]; } if (p->format & AFMT_VCHAN) info->fmtlist[j] = p->format; else info->fmtlist[j] = VCHAN_DEFAULT_FORMAT; info->caps.fmtlist = info->fmtlist + ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET); CHN_UNLOCK(p); c->flags |= CHN_F_VIRTUAL; return (info); } static int vchan_free(kobj_t obj, void *data) { free(data, M_DEVBUF); return (0); } static int vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchan_info *info; info = data; CHN_LOCKASSERT(info->channel); if (!snd_fmtvalid(format, info->caps.fmtlist)) return (-1); return (0); } static uint32_t vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchan_info *info; info = data; CHN_LOCKASSERT(info->channel); return (info->caps.maxspeed); } static int vchan_trigger(kobj_t obj, void *data, int go) { struct vchan_info *info; struct pcm_channel *c, *p; int ret, otrigger; info = data; c = info->channel; p = c->parentchannel; CHN_LOCKASSERT(c); if (!PCMTRIG_COMMON(go) || go == info->trigger) return (0); CHN_UNLOCK(c); CHN_LOCK(p); otrigger = info->trigger; info->trigger = go; switch (go) { case PCMTRIG_START: if (otrigger != PCMTRIG_START) CHN_INSERT_HEAD(p, c, children.busy); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (otrigger == PCMTRIG_START) CHN_REMOVE(p, c, children.busy); break; default: break; } ret = chn_notify(p, CHN_N_TRIGGER); CHN_LOCK(c); if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c)) ret = vchan_sync(c); CHN_UNLOCK(c); CHN_UNLOCK(p); CHN_LOCK(c); return (ret); } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchan_info *info; struct pcm_channel *c; uint32_t pformat, pspeed, pflags, i; info = data; c = info->channel; pformat = c->parentchannel->format; pspeed = c->parentchannel->speed; pflags = c->parentchannel->flags; CHN_LOCKASSERT(c); if (pflags & CHN_F_VCHAN_DYNAMIC) { info->caps.fmtlist = info->fmtlist; if (pformat & AFMT_VCHAN) { for (i = 0; info->caps.fmtlist[i] != 0; i++) { if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH) continue; break; } info->caps.fmtlist[i] = pformat; } if (c->format & AFMT_PASSTHROUGH) info->caps.minspeed = c->speed; else info->caps.minspeed = pspeed; info->caps.maxspeed = info->caps.minspeed; } else { info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET; if (pformat & AFMT_VCHAN) info->caps.fmtlist[0] = pformat; else { device_printf(c->dev, "%s(): invalid vchan format 0x%08x", __func__, pformat); info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT; } info->caps.minspeed = pspeed; info->caps.maxspeed = info->caps.minspeed; } return (&info->caps); } static struct pcmchan_matrix * vchan_getmatrix(kobj_t obj, void *data, uint32_t format) { return (feeder_matrix_format_map(format)); } static kobj_method_t vchan_methods[] = { KOBJMETHOD(channel_init, vchan_init), KOBJMETHOD(channel_free, vchan_free), KOBJMETHOD(channel_setformat, vchan_setformat), KOBJMETHOD(channel_setspeed, vchan_setspeed), KOBJMETHOD(channel_trigger, vchan_trigger), KOBJMETHOD(channel_getcaps, vchan_getcaps), KOBJMETHOD(channel_getmatrix, vchan_getmatrix), KOBJMETHOD_END }; CHANNEL_DECLARE(vchan); static void vchan_getparentchannel(struct snddev_info *d, struct pcm_channel **wrch, struct pcm_channel **rdch) { struct pcm_channel **ch, *wch, *rch, *c; KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__)); PCM_BUSYASSERT(d); PCM_UNLOCKASSERT(d); wch = NULL; rch = NULL; CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch; if (c->flags & CHN_F_VIRTUAL) { /* Sanity check */ if (*ch != NULL && *ch != c->parentchannel) { CHN_UNLOCK(c); *ch = NULL; break; } - } else if (c->flags & CHN_F_HAS_VCHAN) { + } else { /* No way!! */ if (*ch != NULL) { CHN_UNLOCK(c); *ch = NULL; break; } *ch = c; } CHN_UNLOCK(c); } if (wrch != NULL) *wrch = wch; if (rdch != NULL) *rdch = rch; } static int sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - int direction, vchancount; - int err, cnt; + int err, enabled, flag; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: - direction = PCMDIR_PLAY; - vchancount = d->pvchancount; - cnt = d->playcount; + /* Exit if we do not support this direction. */ + if (d->playcount < 1) { + PCM_UNLOCK(d); + return (ENODEV); + } + flag = SD_F_PVCHANS; break; case VCHAN_REC: - direction = PCMDIR_REC; - vchancount = d->rvchancount; - cnt = d->reccount; + if (d->reccount < 1) { + PCM_UNLOCK(d); + return (ENODEV); + } + flag = SD_F_RVCHANS; break; default: PCM_UNLOCK(d); return (EINVAL); - break; } - if (cnt < 1) { - PCM_UNLOCK(d); - return (ENODEV); - } + enabled = (d->flags & flag) != 0; PCM_ACQUIRE(d); PCM_UNLOCK(d); - cnt = vchancount; - err = sysctl_handle_int(oidp, &cnt, 0, req); - - if (err == 0 && req->newptr != NULL && vchancount != cnt) { - if (cnt < 0) - cnt = 0; - if (cnt > SND_MAXVCHANS) - cnt = SND_MAXVCHANS; - err = vchan_setnew(d, direction, cnt); + err = sysctl_handle_int(oidp, &enabled, 0, req); + if (err != 0 || req->newptr == NULL) { + PCM_RELEASE_QUICK(d); + return (err); } + if (enabled <= 0) + d->flags &= ~flag; + else + d->flags |= flag; + PCM_RELEASE_QUICK(d); - return err; + return (0); } static int sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c; uint32_t dflags; int direction, ret; char dtype[16]; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: + if ((d->flags & SD_F_PVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_PLAY; break; case VCHAN_REC: + if ((d->flags & SD_F_RVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_REC; break; default: PCM_UNLOCK(d); return (EINVAL); - break; } PCM_ACQUIRE(d); PCM_UNLOCK(d); if (direction == PCMDIR_PLAY) vchan_getparentchannel(d, &c, NULL); else vchan_getparentchannel(d, NULL, &c); if (c == NULL) { PCM_RELEASE_QUICK(d); return (EINVAL); } KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); CHN_LOCK(c); if (c->flags & CHN_F_VCHAN_PASSTHROUGH) strlcpy(dtype, "passthrough", sizeof(dtype)); else if (c->flags & CHN_F_VCHAN_ADAPTIVE) strlcpy(dtype, "adaptive", sizeof(dtype)); else strlcpy(dtype, "fixed", sizeof(dtype)); CHN_UNLOCK(c); ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req); if (ret == 0 && req->newptr != NULL) { if (strcasecmp(dtype, "passthrough") == 0 || strcmp(dtype, "1") == 0) dflags = CHN_F_VCHAN_PASSTHROUGH; else if (strcasecmp(dtype, "adaptive") == 0 || strcmp(dtype, "2") == 0) dflags = CHN_F_VCHAN_ADAPTIVE; else if (strcasecmp(dtype, "fixed") == 0 || strcmp(dtype, "0") == 0) dflags = 0; else { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_LOCK(c); if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) || (c->flags & CHN_F_PASSTHROUGH)) { CHN_UNLOCK(c); PCM_RELEASE_QUICK(d); return (0); } c->flags &= ~CHN_F_VCHAN_DYNAMIC; c->flags |= dflags; CHN_UNLOCK(c); } PCM_RELEASE_QUICK(d); return (ret); } /* * On the fly vchan rate/format settings */ #define VCHAN_ACCESSIBLE(c) (!((c)->flags & (CHN_F_PASSTHROUGH | \ CHN_F_EXCLUSIVE)) && \ (((c)->flags & CHN_F_VCHAN_DYNAMIC) || \ CHN_STOPPED(c))) static int sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch; struct pcmchan_caps *caps; - int *vchanrate, vchancount, direction, ret, newspd, restart; + int *vchanrate, direction, ret, newspd, restart; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: + if ((d->flags & SD_F_PVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_PLAY; - vchancount = d->pvchancount; vchanrate = &d->pvchanrate; break; case VCHAN_REC: + if ((d->flags & SD_F_RVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_REC; - vchancount = d->rvchancount; vchanrate = &d->rvchanrate; break; default: PCM_UNLOCK(d); return (EINVAL); - break; - } - - if (vchancount < 1) { - PCM_UNLOCK(d); - return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); if (direction == PCMDIR_PLAY) vchan_getparentchannel(d, &c, NULL); else vchan_getparentchannel(d, NULL, &c); if (c == NULL) { PCM_RELEASE_QUICK(d); return (EINVAL); } KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); newspd = *vchanrate; ret = sysctl_handle_int(oidp, &newspd, 0, req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (ret); } if (newspd < feeder_rate_min || newspd > feeder_rate_max) { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_LOCK(c); if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) { if (CHN_STARTED(c)) { chn_abort(c); restart = 1; } else restart = 0; if (feeder_rate_round) { caps = chn_getcaps(c); RANGE(newspd, caps->minspeed, caps->maxspeed); newspd = CHANNEL_SETSPEED(c->methods, c->devinfo, newspd); } ret = chn_reset(c, c->format, newspd); if (ret == 0) { if (restart != 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } c->flags |= CHN_F_DIRTY; ret = chn_start(c, 1); } } } *vchanrate = c->speed; CHN_UNLOCK(c); PCM_RELEASE_QUICK(d); return (ret); } static int sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch; uint32_t newfmt; - int *vchanformat, vchancount, direction, ret, restart; + int *vchanformat, direction, ret, restart; char fmtstr[AFMTSTR_LEN]; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: + if ((d->flags & SD_F_PVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_PLAY; - vchancount = d->pvchancount; vchanformat = &d->pvchanformat; break; case VCHAN_REC: + if ((d->flags & SD_F_RVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_REC; - vchancount = d->rvchancount; vchanformat = &d->rvchanformat; break; default: PCM_UNLOCK(d); return (EINVAL); - break; - } - - if (vchancount < 1) { - PCM_UNLOCK(d); - return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); if (direction == PCMDIR_PLAY) vchan_getparentchannel(d, &c, NULL); else vchan_getparentchannel(d, NULL, &c); if (c == NULL) { PCM_RELEASE_QUICK(d); return (EINVAL); } KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); bzero(fmtstr, sizeof(fmtstr)); if (snd_afmt2str(*vchanformat, fmtstr, sizeof(fmtstr)) != *vchanformat) strlcpy(fmtstr, "", sizeof(fmtstr)); ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (ret); } newfmt = snd_str2afmt(fmtstr); if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_LOCK(c); if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) { if (CHN_STARTED(c)) { chn_abort(c); restart = 1; } else restart = 0; ret = chn_reset(c, newfmt, c->speed); if (ret == 0) { if (restart != 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } c->flags |= CHN_F_DIRTY; ret = chn_start(c, 1); } } } *vchanformat = c->format; CHN_UNLOCK(c); PCM_RELEASE_QUICK(d); return (ret); } /* virtual channel interface */ #define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanformat" : "rec.vchanformat" #define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanrate" : "rec.vchanrate" int -vchan_create(struct pcm_channel *parent) +vchan_create(struct pcm_channel *parent, struct pcm_channel **child) { struct snddev_info *d; struct pcm_channel *ch; struct pcmchan_caps *parent_caps; uint32_t vchanfmt, vchanspd; int ret, direction, r; bool save; ret = 0; save = false; d = parent->parentsnddev; PCM_BUSYASSERT(d); CHN_LOCKASSERT(parent); - if (!(parent->flags & CHN_F_BUSY)) - return (EBUSY); - if (!(parent->direction == PCMDIR_PLAY || parent->direction == PCMDIR_REC)) return (EINVAL); CHN_UNLOCK(parent); PCM_LOCK(d); if (parent->direction == PCMDIR_PLAY) { direction = PCMDIR_PLAY_VIRTUAL; vchanfmt = d->pvchanformat; vchanspd = d->pvchanrate; } else { direction = PCMDIR_REC_VIRTUAL; vchanfmt = d->rvchanformat; vchanspd = d->rvchanrate; } /* create a new playback channel */ ch = chn_init(d, parent, &vchan_class, direction, parent); if (ch == NULL) { PCM_UNLOCK(d); CHN_LOCK(parent); return (ENODEV); } PCM_UNLOCK(d); CHN_LOCK(parent); /* * Add us to our parent channel's children in reverse order * so future destruction will pick the last (biggest number) * channel. */ CHN_INSERT_SORT_DESCEND(parent, ch, children); + *child = ch; + if (parent->flags & CHN_F_HAS_VCHAN) return (0); - parent->flags |= CHN_F_HAS_VCHAN; + parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY; parent_caps = chn_getcaps(parent); if (parent_caps == NULL) { ret = EINVAL; goto fail; } if (vchanfmt == 0) { const char *vfmt; CHN_UNLOCK(parent); r = resource_string_value(device_get_name(parent->dev), device_get_unit(parent->dev), VCHAN_FMT_HINT(direction), &vfmt); CHN_LOCK(parent); if (r != 0) vfmt = NULL; if (vfmt != NULL) { vchanfmt = snd_str2afmt(vfmt); if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN)) vchanfmt = 0; } if (vchanfmt == 0) vchanfmt = VCHAN_DEFAULT_FORMAT; save = true; } if (vchanspd == 0) { /* * This is very sad. Few soundcards advertised as being * able to do (insanely) higher/lower speed, but in * reality, they simply can't. At least, we give user chance * to set sane value via kernel hints or sysctl. */ CHN_UNLOCK(parent); r = resource_int_value(device_get_name(parent->dev), device_get_unit(parent->dev), VCHAN_SPD_HINT(direction), &vchanspd); CHN_LOCK(parent); if (r != 0) { /* No saved value, no hint, NOTHING. */ vchanspd = VCHAN_DEFAULT_RATE; RANGE(vchanspd, parent_caps->minspeed, parent_caps->maxspeed); } save = true; } /* * Limit the speed between feeder_rate_min <-> feeder_rate_max. */ RANGE(vchanspd, feeder_rate_min, feeder_rate_max); if (feeder_rate_round) { RANGE(vchanspd, parent_caps->minspeed, parent_caps->maxspeed); vchanspd = CHANNEL_SETSPEED(parent->methods, parent->devinfo, vchanspd); } if ((ret = chn_reset(parent, vchanfmt, vchanspd)) != 0) goto fail; if (save) { /* * Save new value. */ if (direction == PCMDIR_PLAY_VIRTUAL) { d->pvchanformat = parent->format; d->pvchanrate = parent->speed; } else { d->rvchanformat = parent->format; d->rvchanrate = parent->speed; } } /* * If the parent channel supports digital format, * enable passthrough mode. */ if (snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) { parent->flags &= ~CHN_F_VCHAN_DYNAMIC; parent->flags |= CHN_F_VCHAN_PASSTHROUGH; } return (ret); fail: CHN_LOCK(ch); vchan_destroy(ch); + *child = NULL; return (ret); } int vchan_destroy(struct pcm_channel *c) { struct pcm_channel *parent; KASSERT(c != NULL && c->parentchannel != NULL && c->parentsnddev != NULL, ("%s(): invalid channel=%p", __func__, c)); CHN_LOCKASSERT(c); parent = c->parentchannel; PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(parent); CHN_UNLOCK(c); /* remove us from our parent's children list */ CHN_REMOVE(parent, c, children); if (CHN_EMPTY(parent, children)) { parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); chn_reset(parent, parent->format, parent->speed); } CHN_UNLOCK(parent); /* destroy ourselves */ chn_kill(c); CHN_LOCK(parent); return (0); } int #ifdef SND_DEBUG vchan_passthrough(struct pcm_channel *c, const char *caller) #else vchan_sync(struct pcm_channel *c) #endif { int ret; KASSERT(c != NULL && c->parentchannel != NULL && (c->flags & CHN_F_VIRTUAL), ("%s(): invalid passthrough", __func__)); CHN_LOCKASSERT(c); CHN_LOCKASSERT(c->parentchannel); sndbuf_setspd(c->bufhard, c->parentchannel->speed); c->flags |= CHN_F_PASSTHROUGH; ret = feeder_chain(c); c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH); if (ret != 0) c->flags |= CHN_F_DIRTY; #ifdef SND_DEBUG if (snd_passthrough_verbose) { device_printf(c->dev, "%s(%s/%s) %s() -> re-sync err=%d\n", __func__, c->name, c->comm, caller, ret); } #endif return (ret); } -int -vchan_setnew(struct snddev_info *d, int direction, int newcnt) -{ - struct pcm_channel *c, *ch, *nch; - struct pcmchan_caps *caps; - int i, err, vcnt; - - PCM_BUSYASSERT(d); - - if ((direction == PCMDIR_PLAY && d->playcount < 1) || - (direction == PCMDIR_REC && d->reccount < 1)) - return (ENODEV); - - if (!(d->flags & SD_F_AUTOVCHAN)) - return (EINVAL); - - if (newcnt < 0 || newcnt > SND_MAXVCHANS) - return (E2BIG); - - if (direction == PCMDIR_PLAY) - vcnt = d->pvchancount; - else if (direction == PCMDIR_REC) - vcnt = d->rvchancount; - else - return (EINVAL); - - if (newcnt > vcnt) { - /* add new vchans - find a parent channel first */ - ch = NULL; - CHN_FOREACH(c, d, channels.pcm) { - CHN_LOCK(c); - if (c->direction == direction && - ((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 && - !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) { - /* - * Reuse hw channel with vchans already - * created. - */ - if (c->flags & CHN_F_HAS_VCHAN) { - ch = c; - break; - } - /* - * No vchans ever created, look for - * channels with supported formats. - */ - caps = chn_getcaps(c); - if (caps == NULL) { - CHN_UNLOCK(c); - continue; - } - for (i = 0; caps->fmtlist[i] != 0; i++) { - if (caps->fmtlist[i] & AFMT_CONVERTIBLE) - break; - } - if (caps->fmtlist[i] != 0) { - ch = c; - break; - } - } - CHN_UNLOCK(c); - } - if (ch == NULL) - return (EBUSY); - ch->flags |= CHN_F_BUSY; - err = 0; - while (err == 0 && newcnt > vcnt) { - err = vchan_create(ch); - if (err == 0) - vcnt++; - else if (err == E2BIG && newcnt > vcnt) - device_printf(d->dev, - "%s: err=%d Maximum channel reached.\n", - __func__, err); - } - if (vcnt == 0) - ch->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(ch); - if (err != 0) - return (err); - } else if (newcnt < vcnt) { - CHN_FOREACH(c, d, channels.pcm) { - CHN_LOCK(c); - if (c->direction != direction || - CHN_EMPTY(c, children) || - !(c->flags & CHN_F_HAS_VCHAN)) { - CHN_UNLOCK(c); - continue; - } - CHN_FOREACH_SAFE(ch, c, nch, children) { - CHN_LOCK(ch); - if (vcnt == 1 && ch->flags & CHN_F_BUSY) { - CHN_UNLOCK(ch); - break; - } - if (!(ch->flags & CHN_F_BUSY)) { - err = vchan_destroy(ch); - if (err == 0) - vcnt--; - } else - CHN_UNLOCK(ch); - if (vcnt == newcnt) - break; - } - CHN_UNLOCK(c); - break; - } - } - - return (0); -} - -void -vchan_setmaxauto(struct snddev_info *d, int num) -{ - PCM_BUSYASSERT(d); - - if (num < 0) - return; - - if (num >= 0 && d->pvchancount > num) - (void)vchan_setnew(d, PCMDIR_PLAY, num); - else if (num > 0 && d->pvchancount == 0) - (void)vchan_setnew(d, PCMDIR_PLAY, 1); - - if (num >= 0 && d->rvchancount > num) - (void)vchan_setnew(d, PCMDIR_REC, num); - else if (num > 0 && d->rvchancount == 0) - (void)vchan_setnew(d, PCMDIR_REC, 1); -} - static int -sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int i, v, error; - v = snd_maxautovchans; + v = snd_vchans_enable; error = sysctl_handle_int(oidp, &v, 0, req); - if (error == 0 && req->newptr != NULL) { - if (v < 0) - v = 0; - if (v > SND_MAXVCHANS) - v = SND_MAXVCHANS; - snd_maxautovchans = v; - for (i = 0; pcm_devclass != NULL && - i < devclass_get_maxunit(pcm_devclass); i++) { - d = devclass_get_softc(pcm_devclass, i); - if (!PCM_REGISTERED(d)) - continue; - PCM_ACQUIRE_QUICK(d); - vchan_setmaxauto(d, v); - PCM_RELEASE_QUICK(d); - } + if (error != 0 || req->newptr == NULL) + return (error); + + snd_vchans_enable = v >= 1; + + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + PCM_ACQUIRE_QUICK(d); + if (snd_vchans_enable) { + if (d->playcount > 0) + d->flags |= SD_F_PVCHANS; + if (d->reccount > 0) + d->flags |= SD_F_RVCHANS; + } else + d->flags &= ~(SD_F_PVCHANS | SD_F_RVCHANS); + PCM_RELEASE_QUICK(d); } - return (error); + + return (0); } -SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, +SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), - sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); + sysctl_hw_snd_vchans_enable, "I", "global virtual channel switch"); void vchan_initsys(device_t dev) { struct snddev_info *d; int unit; unit = device_get_unit(dev); d = device_get_softc(dev); /* Play */ SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, - sysctl_dev_pcm_vchans, "I", "total allocated virtual channel"); + sysctl_dev_pcm_vchans, "I", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanmode, "A", "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); /* Rec */ SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, - sysctl_dev_pcm_vchans, "I", "total allocated virtual channel"); + sysctl_dev_pcm_vchans, "I", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanmode, "A", "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); } diff --git a/sys/dev/sound/pcm/vchan.h b/sys/dev/sound/pcm/vchan.h index 1f0fa058cf71..6f3df200db43 100644 --- a/sys/dev/sound/pcm/vchan.h +++ b/sys/dev/sound/pcm/vchan.h @@ -1,75 +1,72 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 2001 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifndef _SND_VCHAN_H_ #define _SND_VCHAN_H_ -extern int snd_maxautovchans; +extern bool snd_vchans_enable; -int vchan_create(struct pcm_channel *); +int vchan_create(struct pcm_channel *, struct pcm_channel **); int vchan_destroy(struct pcm_channel *); #ifdef SND_DEBUG int vchan_passthrough(struct pcm_channel *, const char *); #define vchan_sync(c) vchan_passthrough(c, __func__) #else int vchan_sync(struct pcm_channel *); #endif #define VCHAN_SYNC_REQUIRED(c) \ (((c)->flags & CHN_F_VIRTUAL) && (((c)->flags & CHN_F_DIRTY) || \ sndbuf_getfmt((c)->bufhard) != (c)->parentchannel->format || \ sndbuf_getspd((c)->bufhard) != (c)->parentchannel->speed)) -int vchan_setnew(struct snddev_info *, int, int); -void vchan_setmaxauto(struct snddev_info *, int); - void vchan_initsys(device_t); /* * Default format / rate */ #define VCHAN_DEFAULT_FORMAT SND_FORMAT(AFMT_S16_LE, 2, 0) #define VCHAN_DEFAULT_RATE 48000 #define VCHAN_PLAY 0 #define VCHAN_REC 1 /* * Offset by +/- 1 so we can distinguish bogus pointer. */ #define VCHAN_SYSCTL_DATA(x, y) \ ((void *)((intptr_t)(((((x) + 1) & 0xfff) << 2) | \ (((VCHAN_##y) + 1) & 0x3)))) #define VCHAN_SYSCTL_DATA_SIZE sizeof(void *) #define VCHAN_SYSCTL_UNIT(x) ((int)(((intptr_t)(x) >> 2) & 0xfff) - 1) #define VCHAN_SYSCTL_DIR(x) ((int)((intptr_t)(x) & 0x3) - 1) #endif /* _SND_VCHAN_H_ */