/* * PCM Interface - mmap * Copyright (c) 2000 by Abramo Bagnara * * 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 "config.h" #include #include #include #include #include #ifdef HAVE_SYS_SHM_H #include #endif #include "pcm_local.h" 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; } if (pcm->ops->mmap) err = pcm->ops->mmap(pcm); else err = -ENOSYS; 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: #ifdef HAVE_SYS_SHM_H 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; #else SYSERR("shm support not available"); return -ENOSYS; #endif 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; /* fall through */ 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) { if (pcm->ops->munmap) return pcm->ops->munmap(pcm); else return -ENOSYS; } 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: #ifdef HAVE_SYS_SHM_H 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; #else SYSERR("shm support not available"); return -ENOSYS; #endif case SND_PCM_AREA_LOCAL: free(i->addr); break; default: assert(0); } i->addr = NULL; } if (pcm->ops->munmap) err = pcm->ops->munmap(pcm); else err = -ENOSYS; if (err < 0) return err; free(pcm->mmap_channels); free(pcm->running_areas); pcm->mmap_channels = NULL; pcm->running_areas = NULL; return 0; } /* called in pcm lock */ 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); snd_pcm_unlock(pcm); /* to avoid deadlock */ err = _snd_pcm_writei(pcm, buf, frames); snd_pcm_lock(pcm); 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); } snd_pcm_unlock(pcm); /* to avoid deadlock */ err = _snd_pcm_writen(pcm, bufs, frames); snd_pcm_lock(pcm); 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; } /* called in pcm lock */ 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); snd_pcm_unlock(pcm); /* to avoid deadlock */ err = _snd_pcm_readi(pcm, buf, frames); snd_pcm_lock(pcm); 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); } snd_pcm_unlock(pcm); /* to avoid deadlock */ err = _snd_pcm_readn(pcm->fast_op_arg, bufs, frames); snd_pcm_lock(pcm); 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; }