| /* |
| * PCM Interface - mmap |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <malloc.h> |
| #include <string.h> |
| #include <sys/poll.h> |
| #include <sys/mman.h> |
| #include <sys/shm.h> |
| #include "pcm_local.h" |
| |
| size_t page_size(void) |
| { |
| long s = sysconf(_SC_PAGE_SIZE); |
| assert(s > 0); |
| return s; |
| } |
| |
| size_t page_align(size_t size) |
| { |
| size_t r; |
| long psz = page_size(); |
| r = size % psz; |
| if (r) |
| return size + psz - r; |
| return size; |
| } |
| |
| size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset) |
| { |
| size_t r; |
| long psz = page_size(); |
| assert(offset); |
| assert(mmap_offset); |
| *mmap_offset = object_offset; |
| object_offset %= psz; |
| *mmap_offset -= object_offset; |
| object_size += object_offset; |
| r = object_size % psz; |
| if (r) |
| r = object_size + psz - r; |
| else |
| r = object_size; |
| *offset = object_offset; |
| return r; |
| } |
| |
| void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) |
| { |
| snd_pcm_sframes_t appl_ptr = *pcm->appl.ptr; |
| appl_ptr -= frames; |
| if (appl_ptr < 0) |
| appl_ptr += pcm->boundary; |
| *pcm->appl.ptr = appl_ptr; |
| } |
| |
| void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) |
| { |
| snd_pcm_uframes_t appl_ptr = *pcm->appl.ptr; |
| appl_ptr += frames; |
| if (appl_ptr >= pcm->boundary) |
| appl_ptr -= pcm->boundary; |
| *pcm->appl.ptr = appl_ptr; |
| } |
| |
| void snd_pcm_mmap_hw_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) |
| { |
| snd_pcm_sframes_t hw_ptr = *pcm->hw.ptr; |
| hw_ptr -= frames; |
| if (hw_ptr < 0) |
| hw_ptr += pcm->boundary; |
| *pcm->hw.ptr = hw_ptr; |
| } |
| |
| void snd_pcm_mmap_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) |
| { |
| snd_pcm_uframes_t hw_ptr = *pcm->hw.ptr; |
| hw_ptr += frames; |
| if (hw_ptr >= pcm->boundary) |
| hw_ptr -= pcm->boundary; |
| *pcm->hw.ptr = hw_ptr; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_mmap_write_areas(snd_pcm_t *pcm, |
| const snd_pcm_channel_area_t *areas, |
| snd_pcm_uframes_t offset, |
| snd_pcm_uframes_t size) |
| { |
| snd_pcm_uframes_t xfer = 0; |
| |
| if (snd_pcm_mmap_playback_avail(pcm) < size) { |
| SNDMSG("too short avail %ld to size %ld", snd_pcm_mmap_playback_avail(pcm), size); |
| return -EPIPE; |
| } |
| while (size > 0) { |
| const snd_pcm_channel_area_t *pcm_areas; |
| snd_pcm_uframes_t pcm_offset; |
| snd_pcm_uframes_t frames = size; |
| snd_pcm_sframes_t result; |
| |
| snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); |
| snd_pcm_areas_copy(pcm_areas, pcm_offset, |
| areas, offset, |
| pcm->channels, |
| frames, pcm->format); |
| result = snd_pcm_mmap_commit(pcm, pcm_offset, frames); |
| if (result < 0) |
| return xfer > 0 ? (snd_pcm_sframes_t)xfer : result; |
| offset += result; |
| xfer += result; |
| size -= result; |
| } |
| return (snd_pcm_sframes_t)xfer; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_mmap_read_areas(snd_pcm_t *pcm, |
| const snd_pcm_channel_area_t *areas, |
| snd_pcm_uframes_t offset, |
| snd_pcm_uframes_t size) |
| { |
| snd_pcm_uframes_t xfer = 0; |
| |
| if (snd_pcm_mmap_capture_avail(pcm) < size) { |
| SNDMSG("too short avail %ld to size %ld", snd_pcm_mmap_capture_avail(pcm), size); |
| return -EPIPE; |
| } |
| while (size > 0) { |
| const snd_pcm_channel_area_t *pcm_areas; |
| snd_pcm_uframes_t pcm_offset; |
| snd_pcm_uframes_t frames = size; |
| snd_pcm_sframes_t result; |
| |
| snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); |
| snd_pcm_areas_copy(areas, offset, |
| pcm_areas, pcm_offset, |
| pcm->channels, |
| frames, pcm->format); |
| result = snd_pcm_mmap_commit(pcm, pcm_offset, frames); |
| if (result < 0) |
| return xfer > 0 ? (snd_pcm_sframes_t)xfer : result; |
| offset += result; |
| xfer += result; |
| size -= result; |
| } |
| return (snd_pcm_sframes_t)xfer; |
| } |
| |
| /** |
| * \brief Write interleaved frames to a PCM using direct buffer (mmap) |
| * \param pcm PCM handle |
| * \param buffer frames containing buffer |
| * \param size frames to be written |
| * \return a positive number of frames actually written otherwise a |
| * negative error code |
| * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) |
| * \retval -EPIPE an underrun occurred |
| * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) |
| * |
| * If the blocking behaviour is selected, then routine waits until |
| * all requested bytes are played or put to the playback ring buffer. |
| * The count of bytes can be less only if a signal or underrun occurred. |
| * |
| * If the non-blocking behaviour is selected, then routine doesn't wait at all. |
| */ |
| snd_pcm_sframes_t snd_pcm_mmap_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) |
| { |
| snd_pcm_channel_area_t areas[pcm->channels]; |
| snd_pcm_areas_from_buf(pcm, areas, (void*)buffer); |
| return snd_pcm_write_areas(pcm, areas, 0, size, |
| snd_pcm_mmap_write_areas); |
| } |
| |
| /** |
| * \brief Write non interleaved frames to a PCM using direct buffer (mmap) |
| * \param pcm PCM handle |
| * \param bufs frames containing buffers (one for each channel) |
| * \param size frames to be written |
| * \return a positive number of frames actually written otherwise a |
| * negative error code |
| * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) |
| * \retval -EPIPE an underrun occurred |
| * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) |
| * |
| * If the blocking behaviour is selected, then routine waits until |
| * all requested bytes are played or put to the playback ring buffer. |
| * The count of bytes can be less only if a signal or underrun occurred. |
| * |
| * If the non-blocking behaviour is selected, then routine doesn't wait at all. |
| */ |
| snd_pcm_sframes_t snd_pcm_mmap_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) |
| { |
| snd_pcm_channel_area_t areas[pcm->channels]; |
| snd_pcm_areas_from_bufs(pcm, areas, bufs); |
| return snd_pcm_write_areas(pcm, areas, 0, size, |
| snd_pcm_mmap_write_areas); |
| } |
| |
| /** |
| * \brief Read interleaved frames from a PCM using direct buffer (mmap) |
| * \param pcm PCM handle |
| * \param buffer frames containing buffer |
| * \param size frames to be written |
| * \return a positive number of frames actually read otherwise a |
| * negative error code |
| * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) |
| * \retval -EPIPE an overrun occurred |
| * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) |
| * |
| * If the blocking behaviour was selected, then routine waits until |
| * all requested bytes are filled. The count of bytes can be less only |
| * if a signal or underrun occurred. |
| * |
| * If the non-blocking behaviour is selected, then routine doesn't wait at all. |
| */ |
| snd_pcm_sframes_t snd_pcm_mmap_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) |
| { |
| snd_pcm_channel_area_t areas[pcm->channels]; |
| snd_pcm_areas_from_buf(pcm, areas, buffer); |
| return snd_pcm_read_areas(pcm, areas, 0, size, |
| snd_pcm_mmap_read_areas); |
| } |
| |
| /** |
| * \brief Read non interleaved frames to a PCM using direct buffer (mmap) |
| * \param pcm PCM handle |
| * \param bufs frames containing buffers (one for each channel) |
| * \param size frames to be written |
| * \return a positive number of frames actually read otherwise a |
| * negative error code |
| * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) |
| * \retval -EPIPE an overrun occurred |
| * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) |
| * |
| * If the blocking behaviour was selected, then routine waits until |
| * all requested bytes are filled. The count of bytes can be less only |
| * if a signal or underrun occurred. |
| * |
| * If the non-blocking behaviour is selected, then routine doesn't wait at all. |
| */ |
| snd_pcm_sframes_t snd_pcm_mmap_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) |
| { |
| snd_pcm_channel_area_t areas[pcm->channels]; |
| snd_pcm_areas_from_bufs(pcm, areas, bufs); |
| return snd_pcm_read_areas(pcm, areas, 0, size, |
| snd_pcm_mmap_read_areas); |
| } |
| |
| int snd_pcm_channel_info_shm(snd_pcm_t *pcm, snd_pcm_channel_info_t *info, int shmid) |
| { |
| switch (pcm->access) { |
| case SND_PCM_ACCESS_MMAP_INTERLEAVED: |
| case SND_PCM_ACCESS_RW_INTERLEAVED: |
| info->first = info->channel * pcm->sample_bits; |
| info->step = pcm->frame_bits; |
| break; |
| case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: |
| case SND_PCM_ACCESS_RW_NONINTERLEAVED: |
| info->first = 0; |
| info->step = pcm->sample_bits; |
| break; |
| default: |
| SNDMSG("invalid access type %d", pcm->access); |
| return -EINVAL; |
| } |
| info->addr = 0; |
| if (pcm->hw_flags & SND_PCM_HW_PARAMS_EXPORT_BUFFER) { |
| info->type = SND_PCM_AREA_SHM; |
| info->u.shm.shmid = shmid; |
| info->u.shm.area = NULL; |
| } else |
| info->type = SND_PCM_AREA_LOCAL; |
| return 0; |
| } |
| |
| int snd_pcm_mmap(snd_pcm_t *pcm) |
| { |
| int err; |
| unsigned int c; |
| assert(pcm); |
| if (CHECK_SANITY(! pcm->setup)) { |
| SNDMSG("PCM not set up"); |
| return -EIO; |
| } |
| if (CHECK_SANITY(pcm->mmap_channels || pcm->running_areas)) { |
| SNDMSG("Already mmapped"); |
| return -EBUSY; |
| } |
| err = pcm->ops->mmap(pcm); |
| if (err < 0) |
| return err; |
| if (pcm->mmap_shadow) |
| return 0; |
| pcm->mmap_channels = calloc(pcm->channels, sizeof(pcm->mmap_channels[0])); |
| if (!pcm->mmap_channels) |
| return -ENOMEM; |
| pcm->running_areas = calloc(pcm->channels, sizeof(pcm->running_areas[0])); |
| if (!pcm->running_areas) { |
| free(pcm->mmap_channels); |
| pcm->mmap_channels = NULL; |
| return -ENOMEM; |
| } |
| for (c = 0; c < pcm->channels; ++c) { |
| snd_pcm_channel_info_t *i = &pcm->mmap_channels[c]; |
| i->channel = c; |
| err = snd_pcm_channel_info(pcm, i); |
| if (err < 0) { |
| free(pcm->mmap_channels); |
| free(pcm->running_areas); |
| pcm->mmap_channels = NULL; |
| pcm->running_areas = NULL; |
| return err; |
| } |
| } |
| for (c = 0; c < pcm->channels; ++c) { |
| snd_pcm_channel_info_t *i = &pcm->mmap_channels[c]; |
| snd_pcm_channel_area_t *a = &pcm->running_areas[c]; |
| char *ptr; |
| size_t size; |
| unsigned int c1; |
| if (i->addr) { |
| a->addr = i->addr; |
| a->first = i->first; |
| a->step = i->step; |
| continue; |
| } |
| size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits; |
| for (c1 = c + 1; c1 < pcm->channels; ++c1) { |
| snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; |
| size_t s; |
| if (i1->type != i->type) |
| continue; |
| switch (i1->type) { |
| case SND_PCM_AREA_MMAP: |
| if (i1->u.mmap.fd != i->u.mmap.fd || |
| i1->u.mmap.offset != i->u.mmap.offset) |
| continue; |
| break; |
| case SND_PCM_AREA_SHM: |
| if (i1->u.shm.shmid != i->u.shm.shmid) |
| continue; |
| break; |
| case SND_PCM_AREA_LOCAL: |
| break; |
| default: |
| assert(0); |
| } |
| s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits; |
| if (s > size) |
| size = s; |
| } |
| size = (size + 7) / 8; |
| size = page_align(size); |
| switch (i->type) { |
| case SND_PCM_AREA_MMAP: |
| ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, i->u.mmap.fd, i->u.mmap.offset); |
| if (ptr == MAP_FAILED) { |
| SYSERR("mmap failed"); |
| return -errno; |
| } |
| i->addr = ptr; |
| break; |
| case SND_PCM_AREA_SHM: |
| if (i->u.shm.shmid < 0) { |
| int id; |
| /* FIXME: safer permission? */ |
| id = shmget(IPC_PRIVATE, size, 0666); |
| if (id < 0) { |
| SYSERR("shmget failed"); |
| return -errno; |
| } |
| i->u.shm.shmid = id; |
| ptr = shmat(i->u.shm.shmid, 0, 0); |
| if (ptr == (void *) -1) { |
| SYSERR("shmat failed"); |
| return -errno; |
| } |
| /* automatically remove segment if not used */ |
| if (shmctl(id, IPC_RMID, NULL) < 0){ |
| SYSERR("shmctl mark remove failed"); |
| return -errno; |
| } |
| i->u.shm.area = snd_shm_area_create(id, ptr); |
| if (i->u.shm.area == NULL) { |
| SYSERR("snd_shm_area_create failed"); |
| return -ENOMEM; |
| } |
| if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED || |
| pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) { |
| unsigned int c1; |
| for (c1 = c + 1; c1 < pcm->channels; c1++) { |
| snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; |
| if (i1->u.shm.shmid < 0) { |
| i1->u.shm.shmid = id; |
| i1->u.shm.area = snd_shm_area_share(i->u.shm.area); |
| } |
| } |
| } |
| } else { |
| ptr = shmat(i->u.shm.shmid, 0, 0); |
| if (ptr == (void*) -1) { |
| SYSERR("shmat failed"); |
| return -errno; |
| } |
| } |
| i->addr = ptr; |
| break; |
| case SND_PCM_AREA_LOCAL: |
| ptr = malloc(size); |
| if (ptr == NULL) { |
| SYSERR("malloc failed"); |
| return -errno; |
| } |
| i->addr = ptr; |
| break; |
| default: |
| assert(0); |
| } |
| for (c1 = c + 1; c1 < pcm->channels; ++c1) { |
| snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; |
| if (i1->type != i->type) |
| continue; |
| switch (i1->type) { |
| case SND_PCM_AREA_MMAP: |
| if (i1->u.mmap.fd != i->u.mmap.fd || |
| i1->u.mmap.offset != i->u.mmap.offset) |
| continue; |
| break; |
| case SND_PCM_AREA_SHM: |
| if (i1->u.shm.shmid != i->u.shm.shmid) |
| continue; |
| /* follow thru */ |
| case SND_PCM_AREA_LOCAL: |
| if (pcm->access != SND_PCM_ACCESS_MMAP_INTERLEAVED && |
| pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED) |
| continue; |
| break; |
| default: |
| assert(0); |
| } |
| i1->addr = i->addr; |
| } |
| a->addr = i->addr; |
| a->first = i->first; |
| a->step = i->step; |
| } |
| return 0; |
| } |
| |
| int snd_pcm_munmap(snd_pcm_t *pcm) |
| { |
| int err; |
| unsigned int c; |
| assert(pcm); |
| if (CHECK_SANITY(! pcm->mmap_channels)) { |
| SNDMSG("Not mmapped"); |
| return -ENXIO; |
| } |
| if (pcm->mmap_shadow) |
| return pcm->ops->munmap(pcm); |
| for (c = 0; c < pcm->channels; ++c) { |
| snd_pcm_channel_info_t *i = &pcm->mmap_channels[c]; |
| unsigned int c1; |
| size_t size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits; |
| if (!i->addr) |
| continue; |
| for (c1 = c + 1; c1 < pcm->channels; ++c1) { |
| snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; |
| size_t s; |
| if (i1->addr != i->addr) |
| continue; |
| i1->addr = NULL; |
| s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits; |
| if (s > size) |
| size = s; |
| } |
| size = (size + 7) / 8; |
| size = page_align(size); |
| switch (i->type) { |
| case SND_PCM_AREA_MMAP: |
| err = munmap(i->addr, size); |
| if (err < 0) { |
| SYSERR("mmap failed"); |
| return -errno; |
| } |
| errno = 0; |
| break; |
| case SND_PCM_AREA_SHM: |
| if (i->u.shm.area) { |
| snd_shm_area_destroy(i->u.shm.area); |
| i->u.shm.area = NULL; |
| if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED || |
| pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) { |
| unsigned int c1; |
| for (c1 = c + 1; c1 < pcm->channels; c1++) { |
| snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; |
| if (i1->u.shm.area) { |
| snd_shm_area_destroy(i1->u.shm.area); |
| i1->u.shm.area = NULL; |
| } |
| } |
| } |
| } |
| break; |
| case SND_PCM_AREA_LOCAL: |
| free(i->addr); |
| break; |
| default: |
| assert(0); |
| } |
| i->addr = NULL; |
| } |
| err = pcm->ops->munmap(pcm); |
| if (err < 0) |
| return err; |
| free(pcm->mmap_channels); |
| free(pcm->running_areas); |
| pcm->mmap_channels = NULL; |
| pcm->running_areas = NULL; |
| return 0; |
| } |
| |
| snd_pcm_sframes_t snd_pcm_write_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset, |
| snd_pcm_uframes_t size) |
| { |
| snd_pcm_uframes_t xfer = 0; |
| snd_pcm_sframes_t err = 0; |
| if (! size) |
| return 0; |
| while (xfer < size) { |
| snd_pcm_uframes_t frames = size - xfer; |
| snd_pcm_uframes_t cont = pcm->buffer_size - offset; |
| if (cont < frames) |
| frames = cont; |
| switch (pcm->access) { |
| case SND_PCM_ACCESS_MMAP_INTERLEAVED: |
| { |
| const snd_pcm_channel_area_t *a = snd_pcm_mmap_areas(pcm); |
| const char *buf = snd_pcm_channel_area_addr(a, offset); |
| err = _snd_pcm_writei(pcm, buf, frames); |
| if (err >= 0) |
| frames = err; |
| break; |
| } |
| case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: |
| { |
| unsigned int channels = pcm->channels; |
| unsigned int c; |
| void *bufs[channels]; |
| const snd_pcm_channel_area_t *areas = snd_pcm_mmap_areas(pcm); |
| for (c = 0; c < channels; ++c) { |
| const snd_pcm_channel_area_t *a = &areas[c]; |
| bufs[c] = snd_pcm_channel_area_addr(a, offset); |
| } |
| err = _snd_pcm_writen(pcm, bufs, frames); |
| if (err >= 0) |
| frames = err; |
| break; |
| } |
| default: |
| SNDMSG("invalid access type %d", pcm->access); |
| return -EINVAL; |
| } |
| if (err < 0) |
| break; |
| xfer += frames; |
| offset = (offset + frames) % pcm->buffer_size; |
| } |
| if (xfer > 0) |
| return xfer; |
| return err; |
| } |
| |
| snd_pcm_sframes_t snd_pcm_read_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset, |
| snd_pcm_uframes_t size) |
| { |
| snd_pcm_uframes_t xfer = 0; |
| snd_pcm_sframes_t err = 0; |
| if (! size) |
| return 0; |
| while (xfer < size) { |
| snd_pcm_uframes_t frames = size - xfer; |
| snd_pcm_uframes_t cont = pcm->buffer_size - offset; |
| if (cont < frames) |
| frames = cont; |
| switch (pcm->access) { |
| case SND_PCM_ACCESS_MMAP_INTERLEAVED: |
| { |
| const snd_pcm_channel_area_t *a = snd_pcm_mmap_areas(pcm); |
| char *buf = snd_pcm_channel_area_addr(a, offset); |
| err = _snd_pcm_readi(pcm, buf, frames); |
| if (err >= 0) |
| frames = err; |
| break; |
| } |
| case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: |
| { |
| snd_pcm_uframes_t channels = pcm->channels; |
| unsigned int c; |
| void *bufs[channels]; |
| const snd_pcm_channel_area_t *areas = snd_pcm_mmap_areas(pcm); |
| for (c = 0; c < channels; ++c) { |
| const snd_pcm_channel_area_t *a = &areas[c]; |
| bufs[c] = snd_pcm_channel_area_addr(a, offset); |
| } |
| err = _snd_pcm_readn(pcm->fast_op_arg, bufs, frames); |
| if (err >= 0) |
| frames = err; |
| break; |
| } |
| default: |
| SNDMSG("invalid access type %d", pcm->access); |
| return -EINVAL; |
| } |
| if (err < 0) |
| break; |
| xfer += frames; |
| offset = (offset + frames) % pcm->buffer_size; |
| } |
| if (xfer > 0) |
| return xfer; |
| return err; |
| } |