| /** | |
| * \file pcm/pcm_hw.c | |
| * \ingroup PCM_Plugins | |
| * \brief PCM HW Plugin Interface | |
| * \author Abramo Bagnara <abramo@alsa-project.org> | |
| * \author Jaroslav Kysela <perex@perex.cz> | |
| * \date 2000-2001 | |
| */ | |
| /* | |
| * PCM - Hardware | |
| * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> | |
| * | |
| * | |
| * This library is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as | |
| * published by the Free Software Foundation; either version 2.1 of | |
| * the License, or (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #include "pcm_local.h" | |
| #include "../control/control_local.h" | |
| #include "../timer/timer_local.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stddef.h> | |
| #include <unistd.h> | |
| #include <stdbool.h> | |
| #include <signal.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/mman.h> | |
| //#define DEBUG_RW /* use to debug readi/writei/readn/writen */ | |
| //#define DEBUG_MMAP /* debug mmap_commit */ | |
| #ifndef PIC | |
| /* entry for static linking */ | |
| const char *_snd_module_pcm_hw = ""; | |
| #endif | |
| #ifndef DOC_HIDDEN | |
| #ifndef F_SETSIG | |
| #define F_SETSIG 10 | |
| #endif | |
| /* | |
| * Compatibility | |
| */ | |
| struct sndrv_pcm_hw_params_old { | |
| unsigned int flags; | |
| unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - | |
| SNDRV_PCM_HW_PARAM_ACCESS + 1]; | |
| struct snd_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME - | |
| SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1]; | |
| unsigned int rmask; | |
| unsigned int cmask; | |
| unsigned int info; | |
| unsigned int msbits; | |
| unsigned int rate_num; | |
| unsigned int rate_den; | |
| sndrv_pcm_uframes_t fifo_size; | |
| unsigned char reserved[64]; | |
| }; | |
| #define SND_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old) | |
| #define SND_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct sndrv_pcm_hw_params_old) | |
| static int use_old_hw_params_ioctl(int fd, unsigned int cmd, snd_pcm_hw_params_t *params); | |
| static snd_pcm_sframes_t snd_pcm_hw_avail_update(snd_pcm_t *pcm); | |
| static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops; | |
| static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops_timer; | |
| /* | |
| * | |
| */ | |
| typedef struct { | |
| int version; | |
| int fd; | |
| int card, device, subdevice; | |
| volatile struct snd_pcm_mmap_status * mmap_status; | |
| struct snd_pcm_mmap_control *mmap_control; | |
| bool mmap_status_fallbacked; | |
| bool mmap_control_fallbacked; | |
| struct snd_pcm_sync_ptr *sync_ptr; | |
| bool prepare_reset_sw_params; | |
| bool perfect_drain; | |
| int period_event; | |
| snd_timer_t *period_timer; | |
| struct pollfd period_timer_pfd; | |
| int period_timer_need_poll; | |
| /* restricted parameters */ | |
| snd_pcm_format_t format; | |
| struct { | |
| int min; | |
| int max; | |
| } rates; | |
| int channels; | |
| int drain_silence; | |
| /* for chmap */ | |
| unsigned int chmap_caps; | |
| snd_pcm_chmap_query_t **chmap_override; | |
| } snd_pcm_hw_t; | |
| #define SNDRV_FILE_PCM_STREAM_PLAYBACK ALSA_DEVICE_DIRECTORY "pcmC%iD%ip" | |
| #define SNDRV_FILE_PCM_STREAM_CAPTURE ALSA_DEVICE_DIRECTORY "pcmC%iD%ic" | |
| #define SNDRV_PCM_VERSION_MAX SNDRV_PROTOCOL_VERSION(2, 0, 9) | |
| /* update appl_ptr with driver */ | |
| #define FAST_PCM_STATE(hw) \ | |
| ((snd_pcm_state_t) (hw)->mmap_status->state) | |
| #define FAST_PCM_TSTAMP(hw) \ | |
| ((hw)->mmap_status->tstamp) | |
| struct timespec snd_pcm_hw_fast_tstamp(snd_pcm_t *pcm) | |
| { | |
| struct timespec res; | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| res = FAST_PCM_TSTAMP(hw); | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 5) > hw->version) | |
| res.tv_nsec *= 1000L; | |
| return res; | |
| } | |
| #endif /* DOC_HIDDEN */ | |
| static int sync_ptr1(snd_pcm_hw_t *hw, unsigned int flags) | |
| { | |
| int err; | |
| hw->sync_ptr->flags = flags; | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_SYNC_PTR, hw->sync_ptr) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_SYNC_PTR failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int issue_avail_min(snd_pcm_hw_t *hw) | |
| { | |
| if (!hw->mmap_control_fallbacked) | |
| return 0; | |
| /* Avoid unexpected change of applptr in kernel space. */ | |
| return sync_ptr1(hw, SNDRV_PCM_SYNC_PTR_APPL); | |
| } | |
| static int issue_applptr(snd_pcm_hw_t *hw) | |
| { | |
| if (!hw->mmap_control_fallbacked) | |
| return 0; | |
| /* Avoid unexpected change of avail_min in kernel space. */ | |
| return sync_ptr1(hw, SNDRV_PCM_SYNC_PTR_AVAIL_MIN); | |
| } | |
| static int request_hwsync(snd_pcm_hw_t *hw) | |
| { | |
| if (!hw->mmap_status_fallbacked) | |
| return 0; | |
| /* | |
| * Query both of control/status data to avoid unexpected change of | |
| * control data in kernel space. | |
| */ | |
| return sync_ptr1(hw, | |
| SNDRV_PCM_SYNC_PTR_HWSYNC | | |
| SNDRV_PCM_SYNC_PTR_APPL | | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN); | |
| } | |
| static int query_status_and_control_data(snd_pcm_hw_t *hw) | |
| { | |
| if (!hw->mmap_control_fallbacked) | |
| return 0; | |
| /* | |
| * Query both of control/status data to avoid unexpected change of | |
| * control data in kernel space. | |
| */ | |
| return sync_ptr1(hw, | |
| SNDRV_PCM_SYNC_PTR_APPL | | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN); | |
| } | |
| static int query_status_data(snd_pcm_hw_t *hw) | |
| { | |
| if (!hw->mmap_status_fallbacked) | |
| return 0; | |
| /* | |
| * Query both of control/status data to avoid unexpected change of | |
| * control data in kernel space. | |
| */ | |
| return sync_ptr1(hw, | |
| SNDRV_PCM_SYNC_PTR_APPL | | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN); | |
| } | |
| static int snd_pcm_hw_clear_timer_queue(snd_pcm_hw_t *hw) | |
| { | |
| if (hw->period_timer_need_poll) { | |
| while (poll(&hw->period_timer_pfd, 1, 0) > 0) { | |
| snd_timer_tread_t rbuf[4]; | |
| snd_timer_read(hw->period_timer, rbuf, sizeof(rbuf)); | |
| } | |
| } else { | |
| snd_timer_tread_t rbuf[4]; | |
| snd_timer_read(hw->period_timer, rbuf, sizeof(rbuf)); | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_poll_descriptors_count(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 2; | |
| } | |
| static int snd_pcm_hw_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| if (space < 2) | |
| return -ENOMEM; | |
| pfds[0].fd = hw->fd; | |
| pfds[0].events = pcm->poll_events | POLLERR | POLLNVAL; | |
| pfds[1].fd = hw->period_timer_pfd.fd; | |
| pfds[1].events = POLLIN | POLLERR | POLLNVAL; | |
| return 2; | |
| } | |
| static int snd_pcm_hw_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned nfds, unsigned short *revents) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| unsigned int events; | |
| if (nfds != 2 || pfds[0].fd != hw->fd || pfds[1].fd != hw->period_timer_pfd.fd) | |
| return -EINVAL; | |
| events = pfds[0].revents; | |
| if (pfds[1].revents & POLLIN) { | |
| snd_pcm_hw_clear_timer_queue(hw); | |
| events |= pcm->poll_events & ~(POLLERR|POLLNVAL); | |
| } | |
| *revents = events; | |
| return 0; | |
| } | |
| static int snd_pcm_hw_nonblock(snd_pcm_t *pcm, int nonblock) | |
| { | |
| long flags; | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if ((flags = fcntl(fd, F_GETFL)) < 0) { | |
| err = -errno; | |
| SYSMSG("F_GETFL failed (%i)", err); | |
| return err; | |
| } | |
| if (nonblock) | |
| flags |= O_NONBLOCK; | |
| else | |
| flags &= ~O_NONBLOCK; | |
| if (fcntl(fd, F_SETFL, flags) < 0) { | |
| err = -errno; | |
| SYSMSG("F_SETFL for O_NONBLOCK failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_async(snd_pcm_t *pcm, int sig, pid_t pid) | |
| { | |
| long flags; | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if ((flags = fcntl(fd, F_GETFL)) < 0) { | |
| err = -errno; | |
| SYSMSG("F_GETFL failed (%i)", err); | |
| return err; | |
| } | |
| if (sig >= 0) | |
| flags |= O_ASYNC; | |
| else | |
| flags &= ~O_ASYNC; | |
| if (fcntl(fd, F_SETFL, flags) < 0) { | |
| err = -errno; | |
| SYSMSG("F_SETFL for O_ASYNC failed (%i)", err); | |
| return err; | |
| } | |
| if (sig < 0) | |
| return 0; | |
| if (fcntl(fd, F_SETSIG, (long)sig) < 0) { | |
| err = -errno; | |
| SYSMSG("F_SETSIG failed (%i)", err); | |
| return err; | |
| } | |
| if (fcntl(fd, F_SETOWN, (long)pid) < 0) { | |
| err = -errno; | |
| SYSMSG("F_SETOWN failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_info(snd_pcm_t *pcm, snd_pcm_info_t * info) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, info) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", err); | |
| return err; | |
| } | |
| /* may be configurable (optional) */ | |
| if (__snd_pcm_info_eld_fixup_check(info)) | |
| return __snd_pcm_info_eld_fixup(info); | |
| return 0; | |
| } | |
| static inline int hw_refine_call(snd_pcm_hw_t *pcm_hw, snd_pcm_hw_params_t *params) | |
| { | |
| /* check for new hw_params structure; it's available from 2.0.2 version of PCM API */ | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 2) <= pcm_hw->version) | |
| return ioctl(pcm_hw->fd, SNDRV_PCM_IOCTL_HW_REFINE, params); | |
| return use_old_hw_params_ioctl(pcm_hw->fd, SND_PCM_IOCTL_HW_REFINE_OLD, params); | |
| } | |
| static int snd_pcm_hw_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err; | |
| if (hw->format != SND_PCM_FORMAT_UNKNOWN) { | |
| err = _snd_pcm_hw_params_set_format(params, hw->format); | |
| if (err < 0) | |
| return err; | |
| } | |
| if (hw->channels > 0) { | |
| err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS, | |
| hw->channels, 0); | |
| if (err < 0) | |
| return err; | |
| } | |
| if (hw->rates.min > 0) { | |
| err = _snd_pcm_hw_param_set_minmax(params, SND_PCM_HW_PARAM_RATE, | |
| hw->rates.min, 0, hw->rates.max + 1, -1); | |
| if (err < 0) | |
| return err; | |
| } | |
| if (hw_refine_call(hw, params) < 0) { | |
| err = -errno; | |
| // SYSMSG("SNDRV_PCM_IOCTL_HW_REFINE failed"); | |
| return err; | |
| } | |
| if (params->info != ~0U) { | |
| params->info &= ~0xf0000000; | |
| if (pcm->tstamp_type != SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY) | |
| params->info |= SND_PCM_INFO_MONOTONIC; | |
| } | |
| return 0; | |
| } | |
| #define hw_param_mask(params,var) \ | |
| &((params)->masks[(var) - SND_PCM_HW_PARAM_FIRST_MASK]) | |
| static int hw_params_call(snd_pcm_hw_t *pcm_hw, snd_pcm_hw_params_t *params) | |
| { | |
| int err; | |
| /* check for new hw_params structure; it's available from 2.0.2 version of PCM API */ | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 2) <= pcm_hw->version) | |
| err = ioctl(pcm_hw->fd, SNDRV_PCM_IOCTL_HW_PARAMS, params); | |
| else | |
| err = use_old_hw_params_ioctl(pcm_hw->fd, SND_PCM_IOCTL_HW_PARAMS_OLD, params); | |
| if (err >= 0 && pcm_hw->version < SNDRV_PROTOCOL_VERSION(2, 0, 17) && params->msbits > 0) { | |
| snd_mask_t *m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); | |
| if (snd_mask_single(m)) { | |
| snd_pcm_format_t format = snd_mask_min(m); | |
| int width = snd_pcm_format_width(format); | |
| if (width > 0 && params->msbits > (unsigned int)width) | |
| params->msbits = width; | |
| } | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_hw_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err; | |
| if (hw_params_call(hw, params) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_HW_PARAMS failed (%i)", err); | |
| return err; | |
| } | |
| params->info &= ~0xf0000000; | |
| if (pcm->tstamp_type != SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY) | |
| params->info |= SND_PCM_INFO_MONOTONIC; | |
| hw->perfect_drain = !!(params->info & SND_PCM_INFO_PERFECT_DRAIN) || | |
| !!(params->flags & SND_PCM_HW_PARAMS_NO_DRAIN_SILENCE); | |
| return query_status_data(hw); | |
| } | |
| static void snd_pcm_hw_close_timer(snd_pcm_hw_t *hw) | |
| { | |
| if (hw->period_timer) { | |
| snd_timer_close(hw->period_timer); | |
| hw->period_timer = NULL; | |
| } | |
| } | |
| static int snd_pcm_hw_change_timer(snd_pcm_t *pcm, int enable) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_timer_params_t params = {0}; | |
| unsigned int suspend, resume; | |
| int err; | |
| if (enable) { | |
| err = snd_timer_hw_open(&hw->period_timer, | |
| "hw-pcm-period-event", | |
| SND_TIMER_CLASS_PCM, SND_TIMER_SCLASS_NONE, | |
| hw->card, hw->device, | |
| (hw->subdevice << 1) | (pcm->stream & 1), | |
| SND_TIMER_OPEN_NONBLOCK | SND_TIMER_OPEN_TREAD); | |
| if (err < 0) { | |
| err = snd_timer_hw_open(&hw->period_timer, | |
| "hw-pcm-period-event", | |
| SND_TIMER_CLASS_PCM, SND_TIMER_SCLASS_NONE, | |
| hw->card, hw->device, | |
| (hw->subdevice << 1) | (pcm->stream & 1), | |
| SND_TIMER_OPEN_NONBLOCK); | |
| return err; | |
| } | |
| if (snd_timer_poll_descriptors_count(hw->period_timer) != 1) { | |
| snd_pcm_hw_close_timer(hw); | |
| return -EINVAL; | |
| } | |
| hw->period_timer_pfd.events = POLLIN; | |
| hw->period_timer_pfd.revents = 0; | |
| snd_timer_poll_descriptors(hw->period_timer, | |
| &hw->period_timer_pfd, 1); | |
| hw->period_timer_need_poll = 0; | |
| suspend = 1<<SND_TIMER_EVENT_MSUSPEND; | |
| resume = 1<<SND_TIMER_EVENT_MRESUME; | |
| /* | |
| * hacks for older kernel drivers | |
| */ | |
| { | |
| int ver = 0; | |
| ioctl(hw->period_timer_pfd.fd, | |
| SNDRV_TIMER_IOCTL_PVERSION, &ver); | |
| /* | |
| * In older versions, check via poll before read() is | |
| * needed because of the confliction between | |
| * TIMER_START and FIONBIO ioctls. | |
| */ | |
| if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 4)) | |
| hw->period_timer_need_poll = 1; | |
| /* | |
| * In older versions, timer uses pause events instead | |
| * suspend/resume events. | |
| */ | |
| if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 5)) { | |
| suspend = 1<<SND_TIMER_EVENT_MPAUSE; | |
| resume = 1<<SND_TIMER_EVENT_MCONTINUE; | |
| } | |
| } | |
| snd_timer_params_set_auto_start(¶ms, 1); | |
| snd_timer_params_set_ticks(¶ms, 1); | |
| INTERNAL(snd_timer_params_set_filter)(¶ms, (1<<SND_TIMER_EVENT_TICK) | | |
| suspend | resume); | |
| err = snd_timer_params(hw->period_timer, ¶ms); | |
| if (err < 0) { | |
| snd_pcm_hw_close_timer(hw); | |
| return err; | |
| } | |
| err = snd_timer_start(hw->period_timer); | |
| if (err < 0) { | |
| snd_pcm_hw_close_timer(hw); | |
| return err; | |
| } | |
| pcm->fast_ops = &snd_pcm_hw_fast_ops_timer; | |
| } else { | |
| snd_pcm_hw_close_timer(hw); | |
| pcm->fast_ops = &snd_pcm_hw_fast_ops; | |
| hw->period_event = 0; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_hw_free(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| snd_pcm_hw_change_timer(pcm, 0); | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_HW_FREE) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_HW_FREE failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err = 0; | |
| int old_period_event = sw_get_period_event(params); | |
| sw_set_period_event(params, 0); | |
| if ((snd_pcm_tstamp_t) params->tstamp_mode == pcm->tstamp_mode && | |
| (snd_pcm_tstamp_type_t) params->tstamp_type == pcm->tstamp_type && | |
| params->period_step == pcm->period_step && | |
| params->start_threshold == pcm->start_threshold && | |
| params->stop_threshold == pcm->stop_threshold && | |
| params->silence_threshold == pcm->silence_threshold && | |
| params->silence_size == pcm->silence_size && | |
| old_period_event == hw->period_event) { | |
| hw->mmap_control->avail_min = params->avail_min; | |
| err = issue_avail_min(hw); | |
| goto out; | |
| } | |
| if (params->tstamp_type == SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW && | |
| hw->version < SNDRV_PROTOCOL_VERSION(2, 0, 12)) { | |
| SYSMSG("Kernel doesn't support SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW"); | |
| err = -EINVAL; | |
| goto out; | |
| } | |
| if (params->tstamp_type == SND_PCM_TSTAMP_TYPE_MONOTONIC && | |
| hw->version < SNDRV_PROTOCOL_VERSION(2, 0, 5)) { | |
| SYSMSG("Kernel doesn't support SND_PCM_TSTAMP_TYPE_MONOTONIC"); | |
| err = -EINVAL; | |
| goto out; | |
| } | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_SW_PARAMS, params) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_SW_PARAMS failed (%i)", err); | |
| goto out; | |
| } | |
| hw->prepare_reset_sw_params = false; | |
| if ((snd_pcm_tstamp_type_t) params->tstamp_type != pcm->tstamp_type) { | |
| if (hw->version < SNDRV_PROTOCOL_VERSION(2, 0, 12)) { | |
| int on = (snd_pcm_tstamp_type_t) params->tstamp_type == | |
| SND_PCM_TSTAMP_TYPE_MONOTONIC; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_TSTAMP, &on) < 0) { | |
| err = -errno; | |
| SNDMSG("TSTAMP failed"); | |
| goto out; | |
| } | |
| } | |
| pcm->tstamp_type = params->tstamp_type; | |
| } | |
| hw->mmap_control->avail_min = params->avail_min; | |
| if (hw->period_event != old_period_event) { | |
| err = snd_pcm_hw_change_timer(pcm, old_period_event); | |
| if (err < 0) | |
| goto out; | |
| hw->period_event = old_period_event; | |
| } | |
| out: | |
| sw_set_period_event(params, old_period_event); | |
| return err; | |
| } | |
| static int snd_pcm_hw_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| struct snd_pcm_channel_info i; | |
| int fd = hw->fd, err; | |
| i.channel = info->channel; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_CHANNEL_INFO, &i) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_CHANNEL_INFO failed (%i)", err); | |
| return err; | |
| } | |
| info->channel = i.channel; | |
| info->addr = 0; | |
| info->first = i.first; | |
| info->step = i.step; | |
| info->type = SND_PCM_AREA_MMAP; | |
| info->u.mmap.fd = fd; | |
| info->u.mmap.offset = i.offset; | |
| return 0; | |
| } | |
| static int snd_pcm_hw_status(snd_pcm_t *pcm, snd_pcm_status_t * status) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 13) > hw->version) { | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_STATUS, status) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_STATUS failed (%i)", err); | |
| return err; | |
| } | |
| } else { | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_STATUS_EXT, status) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_STATUS_EXT failed (%i)", err); | |
| return err; | |
| } | |
| } | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 5) > hw->version) { | |
| status->tstamp.tv_nsec *= 1000L; | |
| status->trigger_tstamp.tv_nsec *= 1000L; | |
| } | |
| return 0; | |
| } | |
| static snd_pcm_state_t snd_pcm_hw_state(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| /* the -ENODEV may come from the snd_disconnect_ioctl() or | |
| snd_power_wait() in kernel */ | |
| if (query_status_data(hw) == -ENODEV) | |
| return SND_PCM_STATE_DISCONNECTED; | |
| return (snd_pcm_state_t) hw->mmap_status->state; | |
| } | |
| static int snd_pcm_hw_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_DELAY, delayp) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_DELAY failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_hwsync(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 3) <= hw->version) { | |
| if (hw->mmap_status_fallbacked) { | |
| err = request_hwsync(hw); | |
| if (err < 0) | |
| return err; | |
| } else { | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_HWSYNC) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_HWSYNC failed (%i)", err); | |
| return err; | |
| } | |
| } | |
| } else { | |
| snd_pcm_sframes_t delay; | |
| int err = snd_pcm_hw_delay(pcm, &delay); | |
| if (err < 0) { | |
| switch (FAST_PCM_STATE(hw)) { | |
| case SND_PCM_STATE_PREPARED: | |
| case SND_PCM_STATE_SUSPENDED: | |
| return 0; | |
| default: | |
| return err; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_prepare(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_pcm_sw_params_t sw_params; | |
| int fd = hw->fd, err; | |
| if (hw->prepare_reset_sw_params) { | |
| snd_pcm_sw_params_current_no_lock(pcm, &sw_params); | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sw_params) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_SW_PARAMS failed (%i)", err); | |
| return err; | |
| } | |
| hw->prepare_reset_sw_params = false; | |
| } | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_PREPARE) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_PREPARE failed (%i)", err); | |
| return err; | |
| } | |
| return query_status_and_control_data(hw); | |
| } | |
| static int snd_pcm_hw_reset(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_RESET) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_RESET failed (%i)", err); | |
| return err; | |
| } | |
| return query_status_and_control_data(hw); | |
| } | |
| static int snd_pcm_hw_start(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err; | |
| #if 0 | |
| assert(pcm->stream != SND_PCM_STREAM_PLAYBACK || | |
| snd_pcm_mmap_playback_hw_avail(pcm) > 0); | |
| #endif | |
| issue_applptr(hw); | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_START) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_START failed (%i)", err); | |
| #if 0 | |
| if (err == -EBADFD) | |
| SNDERR("PCM state = %s", snd_pcm_state_name(snd_pcm_hw_state(pcm))); | |
| #endif | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_drop(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err; | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_DROP) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_DROP failed (%i)", err); | |
| return err; | |
| } else { | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_drain(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_pcm_sw_params_t sw_params; | |
| snd_pcm_uframes_t silence_size; | |
| int err; | |
| if (pcm->stream != SND_PCM_STREAM_PLAYBACK) | |
| goto __skip_silence; | |
| /* stream probably in SETUP, prevent divide by zero */ | |
| if (pcm->period_size == 0) | |
| goto __skip_silence; | |
| if (hw->drain_silence == 0 || hw->perfect_drain) | |
| goto __skip_silence; | |
| snd_pcm_sw_params_current_no_lock(pcm, &sw_params); | |
| if (hw->drain_silence > 0) { | |
| silence_size = (pcm->rate * hw->drain_silence) / 1000; | |
| goto __manual_silence; | |
| } | |
| /* compute end silence size, align to period size + extra time */ | |
| if ((pcm->boundary % pcm->period_size) == 0) { | |
| silence_size = pcm->period_size - (*pcm->appl.ptr % pcm->period_size); | |
| if (silence_size == pcm->period_size) | |
| silence_size = 0; | |
| } else { | |
| /* it not not easy to compute the period crossing point | |
| * in this case because the period is not aligned to the boundary | |
| * - use the full range (one period) in this case | |
| */ | |
| silence_size = pcm->period_size; | |
| } | |
| silence_size += pcm->rate / 10; /* 1/10th of second */ | |
| __manual_silence: | |
| if (sw_params.silence_size < silence_size) { | |
| /* fill the silence soon as possible (in the bellow ioctl | |
| * or the next period wake up) | |
| */ | |
| sw_params.silence_threshold = pcm->buffer_size; | |
| if (silence_size > pcm->buffer_size) | |
| silence_size = pcm->buffer_size; | |
| sw_params.silence_size = silence_size; | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sw_params) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_SW_PARAMS failed (%i)", err); | |
| return err; | |
| } | |
| hw->prepare_reset_sw_params = true; | |
| } | |
| __skip_silence: | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_DRAIN) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_DRAIN failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_pause(snd_pcm_t *pcm, int enable) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err; | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_PAUSE, enable) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_PAUSE failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_rewindable(snd_pcm_t *pcm) | |
| { | |
| return snd_pcm_mmap_hw_rewindable(pcm); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err; | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_REWIND, &frames) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_REWIND failed (%i)", err); | |
| return err; | |
| } | |
| err = query_status_and_control_data(hw); | |
| if (err < 0) | |
| return err; | |
| return frames; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_forwardable(snd_pcm_t *pcm) | |
| { | |
| return snd_pcm_mmap_avail(pcm); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err; | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 4) <= hw->version) { | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_FORWARD, &frames) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_FORWARD failed (%i)", err); | |
| return err; | |
| } | |
| err = query_status_and_control_data(hw); | |
| if (err < 0) | |
| return err; | |
| return frames; | |
| } else { | |
| snd_pcm_sframes_t avail; | |
| switch (FAST_PCM_STATE(hw)) { | |
| case SNDRV_PCM_STATE_RUNNING: | |
| case SNDRV_PCM_STATE_DRAINING: | |
| case SNDRV_PCM_STATE_PAUSED: | |
| case SNDRV_PCM_STATE_PREPARED: | |
| break; | |
| case SNDRV_PCM_STATE_XRUN: | |
| return -EPIPE; | |
| default: | |
| return -EBADFD; | |
| } | |
| avail = snd_pcm_mmap_avail(pcm); | |
| if (avail < 0) | |
| return 0; | |
| if (frames > (snd_pcm_uframes_t)avail) | |
| frames = avail; | |
| snd_pcm_mmap_appl_forward(pcm, frames); | |
| return frames; | |
| } | |
| } | |
| static int snd_pcm_hw_resume(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd, err; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_RESUME) < 0) { | |
| err = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_RESUME failed (%i)", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int hw_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2) | |
| { | |
| snd_pcm_hw_t *hw1 = pcm1->private_data; | |
| snd_pcm_hw_t *hw2 = pcm2->private_data; | |
| if (ioctl(hw1->fd, SNDRV_PCM_IOCTL_LINK, hw2->fd) < 0) { | |
| SYSMSG("SNDRV_PCM_IOCTL_LINK failed (%i)", -errno); | |
| return -errno; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_hw_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master) | |
| { | |
| if (master->type != SND_PCM_TYPE_HW) { | |
| SYSMSG("Invalid type for SNDRV_PCM_IOCTL_LINK (%i)", master->type); | |
| return -EINVAL; | |
| } | |
| return hw_link(master, pcm); | |
| } | |
| static int snd_pcm_hw_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2) | |
| { | |
| if (pcm2->type != SND_PCM_TYPE_HW) { | |
| if (pcm2->fast_ops->link_slaves) | |
| return pcm2->fast_ops->link_slaves(pcm2->fast_op_arg, pcm1); | |
| return -ENOSYS; | |
| } | |
| return hw_link(pcm1, pcm2); | |
| } | |
| static int snd_pcm_hw_unlink(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_UNLINK) < 0) { | |
| SYSMSG("SNDRV_PCM_IOCTL_UNLINK failed (%i)", -errno); | |
| return -errno; | |
| } | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) | |
| { | |
| int err; | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd; | |
| struct snd_xferi xferi; | |
| xferi.buf = (char*) buffer; | |
| xferi.frames = size; | |
| xferi.result = 0; /* make valgrind happy */ | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi) < 0) | |
| err = -errno; | |
| else | |
| err = query_status_and_control_data(hw); | |
| #ifdef DEBUG_RW | |
| fprintf(stderr, "hw_writei: frames = %li, xferi.result = %li, err = %i\n", size, xferi.result, err); | |
| #endif | |
| if (err < 0) | |
| return snd_pcm_check_error(pcm, err); | |
| return xferi.result; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) | |
| { | |
| int err; | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd; | |
| struct snd_xfern xfern; | |
| memset(&xfern, 0, sizeof(xfern)); /* make valgrind happy */ | |
| xfern.bufs = bufs; | |
| xfern.frames = size; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_WRITEN_FRAMES, &xfern) < 0) | |
| err = -errno; | |
| else | |
| err = query_status_and_control_data(hw); | |
| #ifdef DEBUG_RW | |
| fprintf(stderr, "hw_writen: frames = %li, result = %li, err = %i\n", size, xfern.result, err); | |
| #endif | |
| if (err < 0) | |
| return snd_pcm_check_error(pcm, err); | |
| return xfern.result; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) | |
| { | |
| int err; | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd; | |
| struct snd_xferi xferi; | |
| xferi.buf = buffer; | |
| xferi.frames = size; | |
| xferi.result = 0; /* make valgrind happy */ | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_READI_FRAMES, &xferi) < 0) | |
| err = -errno; | |
| else | |
| err = query_status_and_control_data(hw); | |
| #ifdef DEBUG_RW | |
| fprintf(stderr, "hw_readi: frames = %li, result = %li, err = %i\n", size, xferi.result, err); | |
| #endif | |
| if (err < 0) | |
| return snd_pcm_check_error(pcm, err); | |
| return xferi.result; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) | |
| { | |
| int err; | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int fd = hw->fd; | |
| struct snd_xfern xfern; | |
| memset(&xfern, 0, sizeof(xfern)); /* make valgrind happy */ | |
| xfern.bufs = bufs; | |
| xfern.frames = size; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_READN_FRAMES, &xfern) < 0) | |
| err = -errno; | |
| else | |
| err = query_status_and_control_data(hw); | |
| #ifdef DEBUG_RW | |
| fprintf(stderr, "hw_readn: frames = %li, result = %li, err = %i\n", size, xfern.result, err); | |
| #endif | |
| if (err < 0) | |
| return snd_pcm_check_error(pcm, err); | |
| return xfern.result; | |
| } | |
| static bool map_status_data(snd_pcm_hw_t *hw, struct snd_pcm_sync_ptr *sync_ptr, | |
| bool force_fallback) | |
| { | |
| struct snd_pcm_mmap_status *mmap_status; | |
| bool fallbacked; | |
| mmap_status = MAP_FAILED; | |
| if (!force_fallback) { | |
| mmap_status = mmap(NULL, page_align(sizeof(*mmap_status)), | |
| PROT_READ, MAP_FILE|MAP_SHARED, | |
| hw->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); | |
| } | |
| if (mmap_status == MAP_FAILED || mmap_status == NULL) { | |
| mmap_status = &sync_ptr->s.status; | |
| fallbacked = true; | |
| } else { | |
| fallbacked = false; | |
| } | |
| hw->mmap_status = mmap_status; | |
| return fallbacked; | |
| } | |
| static bool map_control_data(snd_pcm_hw_t *hw, | |
| struct snd_pcm_sync_ptr *sync_ptr, | |
| bool force_fallback) | |
| { | |
| struct snd_pcm_mmap_control *mmap_control; | |
| bool fallbacked; | |
| mmap_control = MAP_FAILED; | |
| if (!force_fallback) { | |
| mmap_control = mmap(NULL, page_align(sizeof(*mmap_control)), | |
| PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, | |
| hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); | |
| } | |
| if (mmap_control == MAP_FAILED || mmap_control == NULL) { | |
| mmap_control = &sync_ptr->c.control; | |
| fallbacked = true; | |
| } else { | |
| fallbacked = false; | |
| } | |
| hw->mmap_control = mmap_control; | |
| return fallbacked; | |
| } | |
| static int map_status_and_control_data(snd_pcm_t *pcm, bool force_fallback) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| struct snd_pcm_sync_ptr *sync_ptr; | |
| int err; | |
| /* Preparation for fallback to failure of mmap(2). */ | |
| sync_ptr = malloc(sizeof(*sync_ptr)); | |
| if (sync_ptr == NULL) | |
| return -ENOMEM; | |
| memset(sync_ptr, 0, sizeof(*sync_ptr)); | |
| hw->mmap_status_fallbacked = | |
| map_status_data(hw, sync_ptr, force_fallback); | |
| hw->mmap_control_fallbacked = | |
| map_control_data(hw, sync_ptr, force_fallback); | |
| /* Any fallback mode needs to keep the buffer. */ | |
| if (hw->mmap_status_fallbacked || hw->mmap_control_fallbacked) { | |
| hw->sync_ptr = sync_ptr; | |
| } else { | |
| free(sync_ptr); | |
| hw->sync_ptr = NULL; | |
| } | |
| /* do not initialize in case of append and keep the values from the | |
| * kernel | |
| */ | |
| if (!(pcm->mode & SND_PCM_APPEND)) { | |
| /* Initialize the data. */ | |
| hw->mmap_control->appl_ptr = 0; | |
| hw->mmap_control->avail_min = 1; | |
| } | |
| snd_pcm_set_hw_ptr(pcm, &hw->mmap_status->hw_ptr, hw->fd, | |
| SNDRV_PCM_MMAP_OFFSET_STATUS + | |
| offsetof(struct snd_pcm_mmap_status, hw_ptr)); | |
| snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, | |
| SNDRV_PCM_MMAP_OFFSET_CONTROL); | |
| if (hw->mmap_control_fallbacked) { | |
| unsigned int flags = 0; | |
| /* read appl_ptr and avail_min from kernel when device opened | |
| * with SND_PCM_APPEND flag | |
| */ | |
| if (pcm->mode & SND_PCM_APPEND) | |
| flags = SNDRV_PCM_SYNC_PTR_APPL | | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN; | |
| err = sync_ptr1(hw, flags); | |
| if (err < 0) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static void unmap_status_data(snd_pcm_hw_t *hw) | |
| { | |
| if (!hw->mmap_status_fallbacked) { | |
| if (munmap((void *)hw->mmap_status, | |
| page_align(sizeof(*hw->mmap_status))) < 0) | |
| SYSMSG("status munmap failed (%u)", errno); | |
| } | |
| } | |
| static void unmap_control_data(snd_pcm_hw_t *hw) | |
| { | |
| if (!hw->mmap_control_fallbacked) { | |
| if (munmap((void *)hw->mmap_control, | |
| page_align(sizeof(*hw->mmap_control))) < 0) | |
| SYSMSG("control munmap failed (%u)", errno); | |
| } | |
| } | |
| static void unmap_status_and_control_data(snd_pcm_hw_t *hw) | |
| { | |
| unmap_status_data(hw); | |
| unmap_control_data(hw); | |
| if (hw->mmap_status_fallbacked || hw->mmap_control_fallbacked) | |
| free(hw->sync_ptr); | |
| hw->mmap_status = NULL; | |
| hw->mmap_control = NULL; | |
| hw->mmap_status_fallbacked = false; | |
| hw->mmap_control_fallbacked = false; | |
| hw->sync_ptr = NULL; | |
| } | |
| static int snd_pcm_hw_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_hw_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_hw_close(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| int err = 0; | |
| if (close(hw->fd)) { | |
| err = -errno; | |
| SYSMSG("close failed (%i)", err); | |
| } | |
| unmap_status_and_control_data(hw); | |
| free(hw); | |
| return err; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_mmap_commit(snd_pcm_t *pcm, | |
| snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, | |
| snd_pcm_uframes_t size) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_pcm_mmap_appl_forward(pcm, size); | |
| issue_applptr(hw); | |
| #ifdef DEBUG_MMAP | |
| fprintf(stderr, "appl_forward: hw_ptr = %li, appl_ptr = %li, size = %li\n", *pcm->hw.ptr, *pcm->appl.ptr, size); | |
| #endif | |
| return size; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_hw_avail_update(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_pcm_uframes_t avail; | |
| query_status_data(hw); | |
| avail = snd_pcm_mmap_avail(pcm); | |
| switch (FAST_PCM_STATE(hw)) { | |
| case SNDRV_PCM_STATE_RUNNING: | |
| if (avail >= pcm->stop_threshold) { | |
| /* SNDRV_PCM_IOCTL_XRUN ioctl has been implemented since PCM kernel API 2.0.1 */ | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 1) <= hw->version) { | |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_XRUN) < 0) | |
| return -errno; | |
| } | |
| /* everything is ok, state == SND_PCM_STATE_XRUN at the moment */ | |
| return -EPIPE; | |
| } | |
| break; | |
| case SNDRV_PCM_STATE_XRUN: | |
| return -EPIPE; | |
| default: | |
| break; | |
| } | |
| return avail; | |
| } | |
| static int snd_pcm_hw_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, | |
| snd_htimestamp_t *tstamp) | |
| { | |
| snd_pcm_sframes_t avail1; | |
| int ok = 0; | |
| /* unfortunately, loop is necessary to ensure valid timestamp */ | |
| while (1) { | |
| avail1 = snd_pcm_hw_avail_update(pcm); | |
| if (avail1 < 0) | |
| return avail1; | |
| if (ok && (snd_pcm_uframes_t)avail1 == *avail) | |
| break; | |
| *avail = avail1; | |
| *tstamp = snd_pcm_hw_fast_tstamp(pcm); | |
| ok = 1; | |
| } | |
| return 0; | |
| } | |
| static void __fill_chmap_ctl_id(snd_ctl_elem_id_t *id, int dev, int subdev, | |
| int stream) | |
| { | |
| snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM); | |
| if (stream == SND_PCM_STREAM_PLAYBACK) | |
| snd_ctl_elem_id_set_name(id, "Playback Channel Map"); | |
| else | |
| snd_ctl_elem_id_set_name(id, "Capture Channel Map"); | |
| snd_ctl_elem_id_set_device(id, dev); | |
| snd_ctl_elem_id_set_index(id, subdev); | |
| } | |
| static void fill_chmap_ctl_id(snd_pcm_t *pcm, snd_ctl_elem_id_t *id) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| __fill_chmap_ctl_id(id, hw->device, hw->subdevice, pcm->stream); | |
| } | |
| static int is_chmap_type(int type) | |
| { | |
| return (type >= SND_CTL_TLVT_CHMAP_FIXED && | |
| type <= SND_CTL_TLVT_CHMAP_PAIRED); | |
| } | |
| /** | |
| * \!brief Query the available channel maps | |
| * \param card the card number | |
| * \param dev the PCM device number | |
| * \param subdev the PCM substream index | |
| * \param stream the direction of PCM stream | |
| * \return the NULL-terminated array of integer pointers, or NULL at error. | |
| * | |
| * This function works like snd_pcm_query_chmaps() but it takes the card, | |
| * device, substream and stream numbers instead of the already opened | |
| * snd_pcm_t instance, so that you can query available channel maps of | |
| * a PCM before actually opening it. | |
| * | |
| * As the parameters stand, the query is performed only to the hw PCM | |
| * devices, not the abstracted PCM object in alsa-lib. | |
| */ | |
| snd_pcm_chmap_query_t ** | |
| snd_pcm_query_chmaps_from_hw(int card, int dev, int subdev, | |
| snd_pcm_stream_t stream) | |
| { | |
| snd_ctl_t *ctl; | |
| snd_ctl_elem_id_t id = {0}; | |
| unsigned int tlv[2048], *start; | |
| unsigned int type; | |
| snd_pcm_chmap_query_t **map; | |
| int i, ret, nums; | |
| ret = snd_ctl_hw_open(&ctl, NULL, card, 0); | |
| if (ret < 0) { | |
| SYSMSG("Cannot open the associated CTL"); | |
| return NULL; | |
| } | |
| __fill_chmap_ctl_id(&id, dev, subdev, stream); | |
| ret = snd_ctl_elem_tlv_read(ctl, &id, tlv, sizeof(tlv)); | |
| snd_ctl_close(ctl); | |
| if (ret < 0) { | |
| SYSMSG("Cannot read Channel Map TLV"); | |
| return NULL; | |
| } | |
| #if 0 | |
| for (i = 0; i < 32; i++) | |
| fprintf(stderr, "%02x: %08x\n", i, tlv[i]); | |
| #endif | |
| /* FIXME: the parser below assumes that the TLV only contains | |
| * chmap-related blocks | |
| */ | |
| type = tlv[SNDRV_CTL_TLVO_TYPE]; | |
| if (type != SND_CTL_TLVT_CONTAINER) { | |
| if (!is_chmap_type(type)) { | |
| SYSMSG("Invalid TLV type %d", type); | |
| return NULL; | |
| } | |
| start = tlv; | |
| nums = 1; | |
| } else { | |
| unsigned int *p; | |
| int size; | |
| start = tlv + 2; | |
| size = tlv[SNDRV_CTL_TLVO_LEN]; | |
| nums = 0; | |
| for (p = start; size > 0; ) { | |
| if (!is_chmap_type(p[0])) { | |
| SYSMSG("Invalid TLV type %d", p[0]); | |
| return NULL; | |
| } | |
| nums++; | |
| size -= p[1] + 8; | |
| p += p[1] / 4 + 2; | |
| } | |
| } | |
| map = calloc(nums + 1, sizeof(int *)); | |
| if (!map) | |
| return NULL; | |
| for (i = 0; i < nums; i++) { | |
| map[i] = malloc(start[1] + 8); | |
| if (!map[i]) { | |
| snd_pcm_free_chmaps(map); | |
| return NULL; | |
| } | |
| map[i]->type = start[0] - 0x100; | |
| map[i]->map.channels = start[1] / 4; | |
| memcpy(map[i]->map.pos, start + 2, start[1]); | |
| start += start[1] / 4 + 2; | |
| } | |
| return map; | |
| } | |
| enum { CHMAP_CTL_QUERY, CHMAP_CTL_GET, CHMAP_CTL_SET }; | |
| static int chmap_caps(snd_pcm_hw_t *hw, int type) | |
| { | |
| if (hw->chmap_caps & (1 << type)) | |
| return 1; | |
| if (hw->chmap_caps & (1 << (type + 8))) | |
| return 0; | |
| return 1; | |
| } | |
| static void chmap_caps_set_ok(snd_pcm_hw_t *hw, int type) | |
| { | |
| hw->chmap_caps |= (1 << type); | |
| } | |
| static void chmap_caps_set_error(snd_pcm_hw_t *hw, int type) | |
| { | |
| hw->chmap_caps |= (1 << (type + 8)); | |
| } | |
| static snd_pcm_chmap_query_t **snd_pcm_hw_query_chmaps(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_pcm_chmap_query_t **map; | |
| if (hw->chmap_override) | |
| return _snd_pcm_copy_chmap_query(hw->chmap_override); | |
| if (!chmap_caps(hw, CHMAP_CTL_QUERY)) | |
| return NULL; | |
| map = snd_pcm_query_chmaps_from_hw(hw->card, hw->device, | |
| hw->subdevice, pcm->stream); | |
| if (map) | |
| chmap_caps_set_ok(hw, CHMAP_CTL_QUERY); | |
| else | |
| chmap_caps_set_error(hw, CHMAP_CTL_QUERY); | |
| return map; | |
| } | |
| static snd_pcm_chmap_t *snd_pcm_hw_get_chmap(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_pcm_chmap_t *map; | |
| snd_ctl_t *ctl; | |
| snd_ctl_elem_id_t id = {0}; | |
| snd_ctl_elem_value_t val = {0}; | |
| unsigned int i; | |
| int ret; | |
| if (hw->chmap_override) | |
| return _snd_pcm_choose_fixed_chmap(pcm, hw->chmap_override); | |
| if (!chmap_caps(hw, CHMAP_CTL_GET)) | |
| return NULL; | |
| switch (FAST_PCM_STATE(hw)) { | |
| case SNDRV_PCM_STATE_PREPARED: | |
| case SNDRV_PCM_STATE_RUNNING: | |
| case SNDRV_PCM_STATE_XRUN: | |
| case SNDRV_PCM_STATE_DRAINING: | |
| case SNDRV_PCM_STATE_PAUSED: | |
| case SNDRV_PCM_STATE_SUSPENDED: | |
| break; | |
| default: | |
| SYSMSG("Invalid PCM state for chmap_get: %s", | |
| snd_pcm_state_name(FAST_PCM_STATE(hw))); | |
| return NULL; | |
| } | |
| map = malloc(pcm->channels * sizeof(map->pos[0]) + sizeof(*map)); | |
| if (!map) | |
| return NULL; | |
| map->channels = pcm->channels; | |
| ret = snd_ctl_hw_open(&ctl, NULL, hw->card, 0); | |
| if (ret < 0) { | |
| free(map); | |
| SYSMSG("Cannot open the associated CTL"); | |
| chmap_caps_set_error(hw, CHMAP_CTL_GET); | |
| return NULL; | |
| } | |
| fill_chmap_ctl_id(pcm, &id); | |
| snd_ctl_elem_value_set_id(&val, &id); | |
| ret = snd_ctl_elem_read(ctl, &val); | |
| snd_ctl_close(ctl); | |
| if (ret < 0) { | |
| free(map); | |
| SYSMSG("Cannot read Channel Map ctl"); | |
| chmap_caps_set_error(hw, CHMAP_CTL_GET); | |
| return NULL; | |
| } | |
| for (i = 0; i < pcm->channels; i++) | |
| map->pos[i] = snd_ctl_elem_value_get_integer(&val, i); | |
| chmap_caps_set_ok(hw, CHMAP_CTL_GET); | |
| return map; | |
| } | |
| static int snd_pcm_hw_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| snd_ctl_t *ctl; | |
| snd_ctl_elem_id_t id = {0}; | |
| snd_ctl_elem_value_t val = {0}; | |
| unsigned int i; | |
| int ret; | |
| if (hw->chmap_override) | |
| return -ENXIO; | |
| if (!chmap_caps(hw, CHMAP_CTL_SET)) | |
| return -ENXIO; | |
| if (map->channels > 128) { | |
| SYSMSG("Invalid number of channels %d", map->channels); | |
| return -EINVAL; | |
| } | |
| if (FAST_PCM_STATE(hw) != SNDRV_PCM_STATE_PREPARED) { | |
| SYSMSG("Invalid PCM state for chmap_set: %s", | |
| snd_pcm_state_name(FAST_PCM_STATE(hw))); | |
| return -EBADFD; | |
| } | |
| ret = snd_ctl_hw_open(&ctl, NULL, hw->card, 0); | |
| if (ret < 0) { | |
| SYSMSG("Cannot open the associated CTL"); | |
| chmap_caps_set_error(hw, CHMAP_CTL_SET); | |
| return ret; | |
| } | |
| fill_chmap_ctl_id(pcm, &id); | |
| snd_ctl_elem_value_set_id(&val, &id); | |
| for (i = 0; i < map->channels; i++) | |
| snd_ctl_elem_value_set_integer(&val, i, map->pos[i]); | |
| ret = snd_ctl_elem_write(ctl, &val); | |
| snd_ctl_close(ctl); | |
| if (ret >= 0) | |
| chmap_caps_set_ok(hw, CHMAP_CTL_SET); | |
| else if (ret == -ENOENT || ret == -EPERM || ret == -ENXIO) { | |
| chmap_caps_set_error(hw, CHMAP_CTL_SET); | |
| ret = -ENXIO; | |
| } | |
| if (ret < 0) | |
| SYSMSG("Cannot write Channel Map ctl"); | |
| return ret; | |
| } | |
| static void snd_pcm_hw_dump(snd_pcm_t *pcm, snd_output_t *out) | |
| { | |
| snd_pcm_hw_t *hw = pcm->private_data; | |
| char *name; | |
| int err = snd_card_get_name(hw->card, &name); | |
| if (err < 0) { | |
| SNDERR("cannot get card name"); | |
| return; | |
| } | |
| snd_output_printf(out, "Hardware PCM card %d '%s' device %d subdevice %d\n", | |
| hw->card, name, hw->device, hw->subdevice); | |
| free(name); | |
| if (pcm->setup) { | |
| snd_output_printf(out, "Its setup is:\n"); | |
| snd_pcm_dump_setup(pcm, out); | |
| snd_output_printf(out, " appl_ptr : %li\n", hw->mmap_control->appl_ptr); | |
| snd_output_printf(out, " hw_ptr : %li\n", hw->mmap_status->hw_ptr); | |
| } | |
| } | |
| static const snd_pcm_ops_t snd_pcm_hw_ops = { | |
| .close = snd_pcm_hw_close, | |
| .info = snd_pcm_hw_info, | |
| .hw_refine = snd_pcm_hw_hw_refine, | |
| .hw_params = snd_pcm_hw_hw_params, | |
| .hw_free = snd_pcm_hw_hw_free, | |
| .sw_params = snd_pcm_hw_sw_params, | |
| .channel_info = snd_pcm_hw_channel_info, | |
| .dump = snd_pcm_hw_dump, | |
| .nonblock = snd_pcm_hw_nonblock, | |
| .async = snd_pcm_hw_async, | |
| .mmap = snd_pcm_hw_mmap, | |
| .munmap = snd_pcm_hw_munmap, | |
| .query_chmaps = snd_pcm_hw_query_chmaps, | |
| .get_chmap = snd_pcm_hw_get_chmap, | |
| .set_chmap = snd_pcm_hw_set_chmap, | |
| }; | |
| static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = { | |
| .status = snd_pcm_hw_status, | |
| .state = snd_pcm_hw_state, | |
| .hwsync = snd_pcm_hw_hwsync, | |
| .delay = snd_pcm_hw_delay, | |
| .prepare = snd_pcm_hw_prepare, | |
| .reset = snd_pcm_hw_reset, | |
| .start = snd_pcm_hw_start, | |
| .drop = snd_pcm_hw_drop, | |
| .drain = snd_pcm_hw_drain, | |
| .pause = snd_pcm_hw_pause, | |
| .rewindable = snd_pcm_hw_rewindable, | |
| .rewind = snd_pcm_hw_rewind, | |
| .forwardable = snd_pcm_hw_forwardable, | |
| .forward = snd_pcm_hw_forward, | |
| .resume = snd_pcm_hw_resume, | |
| .link = snd_pcm_hw_link, | |
| .link_slaves = snd_pcm_hw_link_slaves, | |
| .unlink = snd_pcm_hw_unlink, | |
| .writei = snd_pcm_hw_writei, | |
| .writen = snd_pcm_hw_writen, | |
| .readi = snd_pcm_hw_readi, | |
| .readn = snd_pcm_hw_readn, | |
| .avail_update = snd_pcm_hw_avail_update, | |
| .mmap_commit = snd_pcm_hw_mmap_commit, | |
| .htimestamp = snd_pcm_hw_htimestamp, | |
| .poll_descriptors = NULL, | |
| .poll_descriptors_count = NULL, | |
| .poll_revents = NULL, | |
| }; | |
| static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops_timer = { | |
| .status = snd_pcm_hw_status, | |
| .state = snd_pcm_hw_state, | |
| .hwsync = snd_pcm_hw_hwsync, | |
| .delay = snd_pcm_hw_delay, | |
| .prepare = snd_pcm_hw_prepare, | |
| .reset = snd_pcm_hw_reset, | |
| .start = snd_pcm_hw_start, | |
| .drop = snd_pcm_hw_drop, | |
| .drain = snd_pcm_hw_drain, | |
| .pause = snd_pcm_hw_pause, | |
| .rewindable = snd_pcm_hw_rewindable, | |
| .rewind = snd_pcm_hw_rewind, | |
| .forwardable = snd_pcm_hw_forwardable, | |
| .forward = snd_pcm_hw_forward, | |
| .resume = snd_pcm_hw_resume, | |
| .link = snd_pcm_hw_link, | |
| .link_slaves = snd_pcm_hw_link_slaves, | |
| .unlink = snd_pcm_hw_unlink, | |
| .writei = snd_pcm_hw_writei, | |
| .writen = snd_pcm_hw_writen, | |
| .readi = snd_pcm_hw_readi, | |
| .readn = snd_pcm_hw_readn, | |
| .avail_update = snd_pcm_hw_avail_update, | |
| .mmap_commit = snd_pcm_hw_mmap_commit, | |
| .htimestamp = snd_pcm_hw_htimestamp, | |
| .poll_descriptors = snd_pcm_hw_poll_descriptors, | |
| .poll_descriptors_count = snd_pcm_hw_poll_descriptors_count, | |
| .poll_revents = snd_pcm_hw_poll_revents, | |
| }; | |
| /** | |
| * \brief Creates a new hw PCM | |
| * \param pcmp Returns created PCM handle | |
| * \param name Name of PCM | |
| * \param fd File descriptor | |
| * \param sync_ptr_ioctl Boolean flag for sync_ptr ioctl | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name, int fd, | |
| int sync_ptr_ioctl) | |
| { | |
| int ver, mode; | |
| snd_pcm_tstamp_type_t tstamp_type = SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY; | |
| long fmode; | |
| snd_pcm_t *pcm = NULL; | |
| snd_pcm_hw_t *hw = NULL; | |
| snd_pcm_info_t info; | |
| int ret; | |
| assert(pcmp); | |
| memset(&info, 0, sizeof(info)); | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0) { | |
| ret = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", ret); | |
| close(fd); | |
| return ret; | |
| } | |
| if ((fmode = fcntl(fd, F_GETFL)) < 0) { | |
| ret = -errno; | |
| close(fd); | |
| return ret; | |
| } | |
| mode = 0; | |
| if (fmode & O_NONBLOCK) | |
| mode |= SND_PCM_NONBLOCK; | |
| if (fmode & O_ASYNC) | |
| mode |= SND_PCM_ASYNC; | |
| if (fmode & O_APPEND) | |
| mode |= SND_PCM_APPEND; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_PVERSION, &ver) < 0) { | |
| ret = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_PVERSION failed (%i)", ret); | |
| close(fd); | |
| return ret; | |
| } | |
| if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_PCM_VERSION_MAX)) | |
| return -SND_ERROR_INCOMPATIBLE_VERSION; | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 14) <= ver) { | |
| /* inform the protocol version we're supporting */ | |
| unsigned int user_ver = SNDRV_PCM_VERSION; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_USER_PVERSION, &user_ver) < 0) { | |
| ret = -errno; | |
| SNDMSG("USER_PVERSION failed"); | |
| return ret; | |
| } | |
| } | |
| #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 9) <= ver) { | |
| struct timespec timespec; | |
| if (clock_gettime(CLOCK_MONOTONIC, ×pec) == 0) { | |
| int on = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_TTSTAMP, &on) < 0) { | |
| ret = -errno; | |
| SNDMSG("TTSTAMP failed"); | |
| return ret; | |
| } | |
| tstamp_type = SND_PCM_TSTAMP_TYPE_MONOTONIC; | |
| } | |
| } else | |
| #endif | |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 5) <= ver) { | |
| int on = 1; | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_TSTAMP, &on) < 0) { | |
| ret = -errno; | |
| SNDMSG("TSTAMP failed"); | |
| return ret; | |
| } | |
| } | |
| hw = calloc(1, sizeof(snd_pcm_hw_t)); | |
| if (!hw) { | |
| close(fd); | |
| return -ENOMEM; | |
| } | |
| hw->version = ver; | |
| hw->card = info.card; | |
| hw->device = info.device; | |
| hw->subdevice = info.subdevice; | |
| hw->fd = fd; | |
| /* no restriction */ | |
| hw->format = SND_PCM_FORMAT_UNKNOWN; | |
| hw->rates.min = hw->rates.max = 0; | |
| hw->channels = 0; | |
| ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode); | |
| if (ret < 0) { | |
| free(hw); | |
| close(fd); | |
| return ret; | |
| } | |
| pcm->ops = &snd_pcm_hw_ops; | |
| pcm->fast_ops = &snd_pcm_hw_fast_ops; | |
| pcm->private_data = hw; | |
| pcm->poll_fd = fd; | |
| pcm->poll_events = info.stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; | |
| pcm->tstamp_type = tstamp_type; | |
| #ifdef THREAD_SAFE_API | |
| pcm->need_lock = 0; /* hw plugin is thread-safe */ | |
| #endif | |
| pcm->own_state_check = 1; /* skip the common state check */ | |
| ret = map_status_and_control_data(pcm, !!sync_ptr_ioctl); | |
| if (ret < 0) { | |
| snd_pcm_close(pcm); | |
| return ret; | |
| } | |
| *pcmp = pcm; | |
| return 0; | |
| } | |
| /** | |
| * \brief Creates a new hw PCM | |
| * \param pcmp Returns created PCM handle | |
| * \param name Name of PCM | |
| * \param card Number of card | |
| * \param device Number of device | |
| * \param subdevice Number of subdevice | |
| * \param stream PCM Stream | |
| * \param mode PCM Mode | |
| * \param mmap_emulation Obsoleted parameter | |
| * \param sync_ptr_ioctl Use SYNC_PTR ioctl rather than mmap for control structures | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| int snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name, | |
| int card, int device, int subdevice, | |
| snd_pcm_stream_t stream, int mode, | |
| int mmap_emulation ATTRIBUTE_UNUSED, | |
| int sync_ptr_ioctl) | |
| { | |
| char filename[sizeof(SNDRV_FILE_PCM_STREAM_PLAYBACK) + 20]; | |
| const char *filefmt; | |
| int ret = 0, fd = -1; | |
| int attempt = 0; | |
| snd_pcm_info_t info; | |
| int fmode; | |
| snd_ctl_t *ctl; | |
| assert(pcmp); | |
| if ((ret = snd_ctl_hw_open(&ctl, NULL, card, 0)) < 0) | |
| return ret; | |
| switch (stream) { | |
| case SND_PCM_STREAM_PLAYBACK: | |
| filefmt = SNDRV_FILE_PCM_STREAM_PLAYBACK; | |
| break; | |
| case SND_PCM_STREAM_CAPTURE: | |
| filefmt = SNDRV_FILE_PCM_STREAM_CAPTURE; | |
| break; | |
| default: | |
| SNDERR("invalid stream %d", stream); | |
| return -EINVAL; | |
| } | |
| sprintf(filename, filefmt, card, device); | |
| __again: | |
| if (attempt++ > 3) { | |
| ret = -EBUSY; | |
| goto _err; | |
| } | |
| ret = snd_ctl_pcm_prefer_subdevice(ctl, subdevice); | |
| if (ret < 0) | |
| goto _err; | |
| fmode = O_RDWR; | |
| if (mode & SND_PCM_NONBLOCK) | |
| fmode |= O_NONBLOCK; | |
| if (mode & SND_PCM_ASYNC) | |
| fmode |= O_ASYNC; | |
| if (mode & SND_PCM_APPEND) | |
| fmode |= O_APPEND; | |
| fd = snd_open_device(filename, fmode); | |
| if (fd < 0) { | |
| ret = -errno; | |
| SYSMSG("open '%s' failed (%i)", filename, ret); | |
| goto _err; | |
| } | |
| if (subdevice >= 0) { | |
| memset(&info, 0, sizeof(info)); | |
| if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0) { | |
| ret = -errno; | |
| SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", ret); | |
| goto _err; | |
| } | |
| if (info.subdevice != (unsigned int) subdevice) { | |
| close(fd); | |
| fd = -1; | |
| goto __again; | |
| } | |
| } | |
| snd_ctl_close(ctl); | |
| return snd_pcm_hw_open_fd(pcmp, name, fd, sync_ptr_ioctl); | |
| _err: | |
| if (fd >= 0) | |
| close(fd); | |
| snd_ctl_close(ctl); | |
| return ret; | |
| } | |
| /*! \page pcm_plugins | |
| \section pcm_plugins_hw Plugin: hw | |
| This plugin communicates directly with the ALSA kernel driver. It is a raw | |
| communication without any conversions. The emulation of mmap access can be | |
| optionally enabled, but expect worse latency in the case. | |
| The nonblock option specifies whether the device is opened in a non-blocking | |
| manner. Note that the blocking behavior for read/write access won't be | |
| changed by this option. This influences only on the blocking behavior at | |
| opening the device. If you would like to keep the compatibility with the | |
| older ALSA stuff, turn this option off. | |
| \code | |
| pcm.name { | |
| type hw # Kernel PCM | |
| card INT/STR # Card name (string) or number (integer) | |
| [device INT] # Device number (default 0) | |
| [subdevice INT] # Subdevice number (default -1: first available) | |
| [sync_ptr_ioctl BOOL] # Use SYNC_PTR ioctl rather than the direct mmap access for control structures | |
| [nonblock BOOL] # Force non-blocking open mode | |
| [format STR] # Restrict only to the given format | |
| [channels INT] # Restrict only to the given channels | |
| [rate INT] # Restrict only to the given rate | |
| or [rate [INT INT]] # Restrict only to the given rate range (min max) | |
| [chmap MAP] # Override channel maps; MAP is a string array | |
| [drain_silence INT] # Add silence in drain (-1 = auto /default/, 0 = off, > 0 milliseconds) | |
| } | |
| \endcode | |
| \subsection pcm_plugins_hw_funcref Function reference | |
| <UL> | |
| <LI>snd_pcm_hw_open() | |
| <LI>_snd_pcm_hw_open() | |
| </UL> | |
| */ | |
| /** | |
| * \brief Creates a new hw PCM | |
| * \param pcmp Returns created PCM handle | |
| * \param name Name of PCM | |
| * \param root Root configuration node | |
| * \param conf Configuration node with hw PCM description | |
| * \param stream PCM Stream | |
| * \param mode PCM Mode | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| int _snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name, | |
| snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, | |
| snd_pcm_stream_t stream, int mode) | |
| { | |
| snd_config_iterator_t i, next; | |
| long card = -1, device = 0, subdevice = -1; | |
| const char *str; | |
| int err, sync_ptr_ioctl = 0; | |
| int min_rate = 0, max_rate = 0, channels = 0, drain_silence = -1; | |
| snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; | |
| snd_config_t *n; | |
| int nonblock = 1; /* non-block per default */ | |
| snd_pcm_chmap_query_t **chmap = NULL; | |
| snd_pcm_hw_t *hw; | |
| /* look for defaults.pcm.nonblock definition */ | |
| if (snd_config_search(root, "defaults.pcm.nonblock", &n) >= 0) { | |
| err = snd_config_get_bool(n); | |
| if (err >= 0) | |
| nonblock = err; | |
| } | |
| snd_config_for_each(i, next, conf) { | |
| const char *id; | |
| n = snd_config_iterator_entry(i); | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (snd_pcm_conf_generic_id(id)) | |
| continue; | |
| if (strcmp(id, "card") == 0) { | |
| err = snd_config_get_card(n); | |
| if (err < 0) | |
| goto fail; | |
| card = err; | |
| continue; | |
| } | |
| if (strcmp(id, "device") == 0) { | |
| err = snd_config_get_integer(n, &device); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto fail; | |
| } | |
| continue; | |
| } | |
| if (strcmp(id, "subdevice") == 0) { | |
| err = snd_config_get_integer(n, &subdevice); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto fail; | |
| } | |
| continue; | |
| } | |
| if (strcmp(id, "sync_ptr_ioctl") == 0) { | |
| err = snd_config_get_bool(n); | |
| if (err < 0) | |
| continue; | |
| sync_ptr_ioctl = err; | |
| continue; | |
| } | |
| if (strcmp(id, "nonblock") == 0) { | |
| err = snd_config_get_bool(n); | |
| if (err < 0) | |
| continue; | |
| nonblock = err; | |
| continue; | |
| } | |
| if (strcmp(id, "rate") == 0) { | |
| long val; | |
| if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND && | |
| snd_config_is_array(n)) { | |
| snd_config_t *m; | |
| err = snd_config_search(n, "0", &m); | |
| if (err < 0) { | |
| SNDERR("array expected for rate compound"); | |
| goto fail; | |
| } | |
| err = snd_config_get_integer(m, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid type for rate.0"); | |
| goto fail; | |
| } | |
| min_rate = max_rate = val; | |
| err = snd_config_search(n, "1", &m); | |
| if (err >= 0) { | |
| err = snd_config_get_integer(m, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid type for rate.0"); | |
| goto fail; | |
| } | |
| max_rate = val; | |
| } | |
| } else { | |
| err = snd_config_get_integer(n, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto fail; | |
| } | |
| min_rate = max_rate = val; | |
| } | |
| continue; | |
| } | |
| if (strcmp(id, "min_rate") == 0) { | |
| long val; | |
| err = snd_config_get_integer(n, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto fail; | |
| } | |
| min_rate = val; | |
| continue; | |
| } | |
| if (strcmp(id, "max_rate") == 0) { | |
| long val; | |
| err = snd_config_get_integer(n, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto fail; | |
| } | |
| max_rate = val; | |
| continue; | |
| } | |
| if (strcmp(id, "format") == 0) { | |
| err = snd_config_get_string(n, &str); | |
| if (err < 0) { | |
| SNDERR("invalid type for %s", id); | |
| goto fail; | |
| } | |
| format = snd_pcm_format_value(str); | |
| continue; | |
| } | |
| if (strcmp(id, "channels") == 0) { | |
| long val; | |
| err = snd_config_get_integer(n, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto fail; | |
| } | |
| channels = val; | |
| continue; | |
| } | |
| if (strcmp(id, "chmap") == 0) { | |
| snd_pcm_free_chmaps(chmap); | |
| chmap = _snd_pcm_parse_config_chmaps(n); | |
| if (!chmap) { | |
| SNDERR("Invalid channel map for %s", id); | |
| err = -EINVAL; | |
| goto fail; | |
| } | |
| continue; | |
| } | |
| if (strcmp(id, "drain_silence") == 0) { | |
| long val; | |
| err = snd_config_get_integer(n, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto fail; | |
| } | |
| drain_silence = val; | |
| continue; | |
| } | |
| SNDERR("Unknown field %s", id); | |
| err = -EINVAL; | |
| goto fail; | |
| } | |
| if (card < 0) { | |
| SNDERR("card is not defined"); | |
| err = -EINVAL; | |
| goto fail; | |
| } | |
| if ((min_rate < 0) || (max_rate < min_rate)) { | |
| SNDERR("min_rate - max_rate configuration invalid"); | |
| err = -EINVAL; | |
| goto fail; | |
| } | |
| err = snd_pcm_hw_open(pcmp, name, card, device, subdevice, stream, | |
| mode | (nonblock ? SND_PCM_NONBLOCK : 0), | |
| 0, sync_ptr_ioctl); | |
| if (err < 0) | |
| goto fail; | |
| if (nonblock && ! (mode & SND_PCM_NONBLOCK)) { | |
| /* revert to blocking mode for read/write access */ | |
| snd_pcm_hw_nonblock(*pcmp, 0); | |
| (*pcmp)->mode = mode; | |
| } else | |
| /* make sure the SND_PCM_NO_xxx flags don't get lost on the | |
| * way */ | |
| (*pcmp)->mode |= mode & (SND_PCM_NO_AUTO_RESAMPLE| | |
| SND_PCM_NO_AUTO_CHANNELS| | |
| SND_PCM_NO_AUTO_FORMAT| | |
| SND_PCM_NO_SOFTVOL); | |
| hw = (*pcmp)->private_data; | |
| if (format != SND_PCM_FORMAT_UNKNOWN) | |
| hw->format = format; | |
| if (channels > 0) | |
| hw->channels = channels; | |
| if (min_rate > 0) { | |
| hw->rates.min = min_rate; | |
| hw->rates.max = max_rate; | |
| } | |
| if (chmap) | |
| hw->chmap_override = chmap; | |
| hw->drain_silence = drain_silence; | |
| return 0; | |
| fail: | |
| snd_pcm_free_chmaps(chmap); | |
| return err; | |
| } | |
| #ifndef DOC_HIDDEN | |
| SND_DLSYM_BUILD_VERSION(_snd_pcm_hw_open, SND_PCM_DLSYM_VERSION); | |
| #endif | |
| /* | |
| * To be removed helpers, but keep binary compatibility at the time | |
| */ | |
| #ifndef DOC_HIDDEN | |
| #define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5)) | |
| #define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5)) | |
| #endif | |
| static void snd_pcm_hw_convert_from_old_params(snd_pcm_hw_params_t *params, | |
| struct sndrv_pcm_hw_params_old *oparams) | |
| { | |
| unsigned int i; | |
| memset(params, 0, sizeof(*params)); | |
| params->flags = oparams->flags; | |
| for (i = 0; i < sizeof(oparams->masks) / sizeof(unsigned int); i++) | |
| params->masks[i].bits[0] = oparams->masks[i]; | |
| memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals)); | |
| params->rmask = __OLD_TO_NEW_MASK(oparams->rmask); | |
| params->cmask = __OLD_TO_NEW_MASK(oparams->cmask); | |
| params->info = oparams->info; | |
| params->msbits = oparams->msbits; | |
| params->rate_num = oparams->rate_num; | |
| params->rate_den = oparams->rate_den; | |
| params->fifo_size = oparams->fifo_size; | |
| } | |
| static void snd_pcm_hw_convert_to_old_params(struct sndrv_pcm_hw_params_old *oparams, | |
| snd_pcm_hw_params_t *params, | |
| unsigned int *cmask) | |
| { | |
| unsigned int i, j; | |
| memset(oparams, 0, sizeof(*oparams)); | |
| oparams->flags = params->flags; | |
| for (i = 0; i < sizeof(oparams->masks) / sizeof(unsigned int); i++) { | |
| oparams->masks[i] = params->masks[i].bits[0]; | |
| for (j = 1; j < sizeof(params->masks[i].bits) / sizeof(unsigned int); j++) | |
| if (params->masks[i].bits[j]) { | |
| *cmask |= 1 << i; | |
| break; | |
| } | |
| } | |
| memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals)); | |
| oparams->rmask = __NEW_TO_OLD_MASK(params->rmask); | |
| oparams->cmask = __NEW_TO_OLD_MASK(params->cmask); | |
| oparams->info = params->info; | |
| oparams->msbits = params->msbits; | |
| oparams->rate_num = params->rate_num; | |
| oparams->rate_den = params->rate_den; | |
| oparams->fifo_size = params->fifo_size; | |
| } | |
| static int use_old_hw_params_ioctl(int fd, unsigned int cmd, snd_pcm_hw_params_t *params) | |
| { | |
| struct sndrv_pcm_hw_params_old oparams; | |
| unsigned int cmask = 0; | |
| int res; | |
| snd_pcm_hw_convert_to_old_params(&oparams, params, &cmask); | |
| res = ioctl(fd, cmd, &oparams); | |
| snd_pcm_hw_convert_from_old_params(params, &oparams); | |
| params->cmask |= cmask; | |
| return res; | |
| } |