/* * intel_sst_interface.c - Intel SST Driver for audio engine * * Copyright (C) 2008-10 Intel Corp * Authors: Vinod Koul * Harsha Priya * Dharageswari R * Jeeja KP * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * This driver exposes the audio engine functionalities to the ALSA * and middleware. * Upper layer interfaces (MAD driver, MMF) to SST driver */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #ifdef CONFIG_MRST_RAR_HANDLER #include #include "../../../drivers/staging/memrar/memrar.h" #endif #include "intel_sst.h" #include "intel_sst_ioctl.h" #include "intel_sst_fw_ipc.h" #include "intel_sst_common.h" #define AM_MODULE 1 #define STREAM_MODULE 0 /** * intel_sst_check_device - checks SST device * * This utility function checks the state of SST device and downlaods FW if * not done, or resumes the device if suspended */ static int intel_sst_check_device(void) { int retval = 0; if (sst_drv_ctx->pmic_state != SND_MAD_INIT_DONE) { pr_warn("Sound card not available\n"); return -EIO; } if (sst_drv_ctx->sst_state == SST_SUSPENDED) { pr_debug("Resuming from Suspended state\n"); retval = intel_sst_resume(sst_drv_ctx->pci); if (retval) { pr_debug("Resume Failed= %#x,abort\n", retval); return retval; } } if (sst_drv_ctx->sst_state == SST_UN_INIT) { /* FW is not downloaded */ retval = sst_download_fw(); if (retval) return -ENODEV; if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) { retval = sst_drv_ctx->rx_time_slot_status; if (retval != RX_TIMESLOT_UNINIT && sst_drv_ctx->pmic_vendor != SND_NC) sst_enable_rx_timeslot(retval); } } return 0; } /** * intel_sst_open - opens a handle to driver * * @i_node: inode structure * @file_ptr:pointer to file * * This function is called by OS when a user space component * tries to get a driver handle. Only one handle at a time * will be allowed */ int intel_sst_open(struct inode *i_node, struct file *file_ptr) { unsigned int retval; mutex_lock(&sst_drv_ctx->stream_lock); pm_runtime_get_sync(&sst_drv_ctx->pci->dev); retval = intel_sst_check_device(); if (retval) { pm_runtime_put(&sst_drv_ctx->pci->dev); mutex_unlock(&sst_drv_ctx->stream_lock); return retval; } if (sst_drv_ctx->encoded_cnt < MAX_ENC_STREAM) { struct ioctl_pvt_data *data = kzalloc(sizeof(struct ioctl_pvt_data), GFP_KERNEL); if (!data) { pm_runtime_put(&sst_drv_ctx->pci->dev); mutex_unlock(&sst_drv_ctx->stream_lock); return -ENOMEM; } sst_drv_ctx->encoded_cnt++; mutex_unlock(&sst_drv_ctx->stream_lock); data->pvt_id = sst_assign_pvt_id(sst_drv_ctx); data->str_id = 0; file_ptr->private_data = (void *)data; pr_debug("pvt_id handle = %d!\n", data->pvt_id); } else { retval = -EUSERS; pm_runtime_put(&sst_drv_ctx->pci->dev); mutex_unlock(&sst_drv_ctx->stream_lock); } return retval; } /** * intel_sst_open_cntrl - opens a handle to driver * * @i_node: inode structure * @file_ptr:pointer to file * * This function is called by OS when a user space component * tries to get a driver handle to /dev/intel_sst_control. * Only one handle at a time will be allowed * This is for control operations only */ int intel_sst_open_cntrl(struct inode *i_node, struct file *file_ptr) { unsigned int retval; /* audio manager open */ mutex_lock(&sst_drv_ctx->stream_lock); pm_runtime_get_sync(&sst_drv_ctx->pci->dev); retval = intel_sst_check_device(); if (retval) { pm_runtime_put(&sst_drv_ctx->pci->dev); mutex_unlock(&sst_drv_ctx->stream_lock); return retval; } if (sst_drv_ctx->am_cnt < MAX_AM_HANDLES) { sst_drv_ctx->am_cnt++; pr_debug("AM handle opened...\n"); file_ptr->private_data = NULL; } else { retval = -EACCES; pm_runtime_put(&sst_drv_ctx->pci->dev); } mutex_unlock(&sst_drv_ctx->stream_lock); return retval; } /** * intel_sst_release - releases a handle to driver * * @i_node: inode structure * @file_ptr: pointer to file * * This function is called by OS when a user space component * tries to release a driver handle. */ int intel_sst_release(struct inode *i_node, struct file *file_ptr) { struct ioctl_pvt_data *data = file_ptr->private_data; pr_debug("Release called, closing app handle\n"); mutex_lock(&sst_drv_ctx->stream_lock); sst_drv_ctx->encoded_cnt--; sst_drv_ctx->stream_cnt--; pm_runtime_put(&sst_drv_ctx->pci->dev); mutex_unlock(&sst_drv_ctx->stream_lock); free_stream_context(data->str_id); kfree(data); return 0; } int intel_sst_release_cntrl(struct inode *i_node, struct file *file_ptr) { /* audio manager close */ mutex_lock(&sst_drv_ctx->stream_lock); sst_drv_ctx->am_cnt--; pm_runtime_put(&sst_drv_ctx->pci->dev); mutex_unlock(&sst_drv_ctx->stream_lock); pr_debug("AM handle closed\n"); return 0; } /** * intel_sst_mmap - mmaps a kernel buffer to user space for copying data * * @vma: vm area structure instance * @file_ptr: pointer to file * * This function is called by OS when a user space component * tries to get mmap memory from driver */ int intel_sst_mmap(struct file *file_ptr, struct vm_area_struct *vma) { int retval, length; struct ioctl_pvt_data *data = (struct ioctl_pvt_data *)file_ptr->private_data; int str_id = data->str_id; void *mem_area; retval = sst_validate_strid(str_id); if (retval) return -EINVAL; length = vma->vm_end - vma->vm_start; pr_debug("called for stream %d length 0x%x\n", str_id, length); if (length > sst_drv_ctx->mmap_len) return -ENOMEM; if (!sst_drv_ctx->mmap_mem) return -EIO; /* round it up to the page bondary */ /*mem_area = (void *)((((unsigned long)sst_drv_ctx->mmap_mem) + PAGE_SIZE - 1) & PAGE_MASK);*/ mem_area = (void *) PAGE_ALIGN((unsigned int) sst_drv_ctx->mmap_mem); /* map the whole physically contiguous area in one piece */ retval = remap_pfn_range(vma, vma->vm_start, virt_to_phys((void *)mem_area) >> PAGE_SHIFT, length, vma->vm_page_prot); if (retval) sst_drv_ctx->streams[str_id].mmapped = false; else sst_drv_ctx->streams[str_id].mmapped = true; pr_debug("mmap ret 0x%x\n", retval); return retval; } /* sets mmap data buffers to play/capture*/ static int intel_sst_mmap_play_capture(u32 str_id, struct snd_sst_mmap_buffs *mmap_buf) { struct sst_stream_bufs *bufs; int retval, i; struct stream_info *stream; struct snd_sst_mmap_buff_entry *buf_entry; struct snd_sst_mmap_buff_entry *tmp_buf; pr_debug("called for str_id %d\n", str_id); retval = sst_validate_strid(str_id); if (retval) return -EINVAL; stream = &sst_drv_ctx->streams[str_id]; if (stream->mmapped != true) return -EIO; if (stream->status == STREAM_UN_INIT || stream->status == STREAM_DECODE) { return -EBADRQC; } stream->curr_bytes = 0; stream->cumm_bytes = 0; tmp_buf = kcalloc(mmap_buf->entries, sizeof(*tmp_buf), GFP_KERNEL); if (!tmp_buf) return -ENOMEM; if (copy_from_user(tmp_buf, (void __user *)mmap_buf->buff, mmap_buf->entries * sizeof(*tmp_buf))) { retval = -EFAULT; goto out_free; } pr_debug("new buffers count %d status %d\n", mmap_buf->entries, stream->status); buf_entry = tmp_buf; for (i = 0; i < mmap_buf->entries; i++) { bufs = kzalloc(sizeof(*bufs), GFP_KERNEL); if (!bufs) { retval = -ENOMEM; goto out_free; } bufs->size = buf_entry->size; bufs->offset = buf_entry->offset; bufs->addr = sst_drv_ctx->mmap_mem; bufs->in_use = false; buf_entry++; /* locking here */ mutex_lock(&stream->lock); list_add_tail(&bufs->node, &stream->bufs); mutex_unlock(&stream->lock); } mutex_lock(&stream->lock); stream->data_blk.condition = false; stream->data_blk.ret_code = 0; if (stream->status == STREAM_INIT && stream->prev != STREAM_UN_INIT && stream->need_draining != true) { stream->prev = stream->status; stream->status = STREAM_RUNNING; if (stream->ops == STREAM_OPS_PLAYBACK) { if (sst_play_frame(str_id) < 0) { pr_warn("play frames fail\n"); mutex_unlock(&stream->lock); retval = -EIO; goto out_free; } } else if (stream->ops == STREAM_OPS_CAPTURE) { if (sst_capture_frame(str_id) < 0) { pr_warn("capture frame fail\n"); mutex_unlock(&stream->lock); retval = -EIO; goto out_free; } } } mutex_unlock(&stream->lock); /* Block the call for reply */ if (!list_empty(&stream->bufs)) { stream->data_blk.on = true; retval = sst_wait_interruptible(sst_drv_ctx, &stream->data_blk); } if (retval >= 0) retval = stream->cumm_bytes; pr_debug("end of play/rec ioctl bytes = %d!!\n", retval); out_free: kfree(tmp_buf); return retval; } /*sets user data buffers to play/capture*/ static int intel_sst_play_capture(struct stream_info *stream, int str_id) { int retval; stream->data_blk.ret_code = 0; stream->data_blk.on = true; stream->data_blk.condition = false; mutex_lock(&stream->lock); if (stream->status == STREAM_INIT && stream->prev != STREAM_UN_INIT) { /* stream is started */ stream->prev = stream->status; stream->status = STREAM_RUNNING; } if (stream->status == STREAM_INIT && stream->prev == STREAM_UN_INIT) { /* stream is not started yet */ pr_debug("Stream isn't in started state %d, prev %d\n", stream->status, stream->prev); } else if ((stream->status == STREAM_RUNNING || stream->status == STREAM_PAUSED) && stream->need_draining != true) { /* stream is started */ if (stream->ops == STREAM_OPS_PLAYBACK || stream->ops == STREAM_OPS_PLAYBACK_DRM) { if (sst_play_frame(str_id) < 0) { pr_warn("play frames failed\n"); mutex_unlock(&stream->lock); return -EIO; } } else if (stream->ops == STREAM_OPS_CAPTURE) { if (sst_capture_frame(str_id) < 0) { pr_warn("capture frames failed\n"); mutex_unlock(&stream->lock); return -EIO; } } } else { mutex_unlock(&stream->lock); return -EIO; } mutex_unlock(&stream->lock); /* Block the call for reply */ retval = sst_wait_interruptible(sst_drv_ctx, &stream->data_blk); if (retval) { stream->status = STREAM_INIT; pr_debug("wait returned error...\n"); } return retval; } /* fills kernel list with buffer addresses for SST DSP driver to process*/ static int snd_sst_fill_kernel_list(struct stream_info *stream, const struct iovec *iovec, unsigned long nr_segs, struct list_head *copy_to_list) { struct sst_stream_bufs *stream_bufs; unsigned long index, mmap_len; unsigned char __user *bufp; unsigned long size, copied_size; int retval = 0, add_to_list = 0; static int sent_offset; static unsigned long sent_index; stream_bufs = kzalloc(sizeof(*stream_bufs), GFP_KERNEL); if (!stream_bufs) return -ENOMEM; stream_bufs->addr = sst_drv_ctx->mmap_mem; #ifdef CONFIG_MRST_RAR_HANDLER if (stream->ops == STREAM_OPS_PLAYBACK_DRM) { for (index = stream->sg_index; index < nr_segs; index++) { __u32 rar_handle; struct sst_stream_bufs *stream_bufs = kzalloc(sizeof(*stream_bufs), GFP_KERNEL); stream->sg_index = index; if (!stream_bufs) return -ENOMEM; if (copy_from_user((void *) &rar_handle, iovec[index].iov_base, sizeof(__u32))) return -EFAULT; stream_bufs->addr = (char *)rar_handle; stream_bufs->in_use = false; stream_bufs->size = iovec[0].iov_len; /* locking here */ mutex_lock(&stream->lock); list_add_tail(&stream_bufs->node, &stream->bufs); mutex_unlock(&stream->lock); } stream->sg_index = index; return retval; } #endif mmap_len = sst_drv_ctx->mmap_len; stream_bufs->addr = sst_drv_ctx->mmap_mem; bufp = stream->cur_ptr; copied_size = 0; if (!stream->sg_index) sent_index = sent_offset = 0; for (index = stream->sg_index; index < nr_segs; index++) { stream->sg_index = index; if (!stream->cur_ptr) bufp = iovec[index].iov_base; size = ((unsigned long)iovec[index].iov_base + iovec[index].iov_len) - (unsigned long) bufp; if ((copied_size + size) > mmap_len) size = mmap_len - copied_size; if (stream->ops == STREAM_OPS_PLAYBACK) { if (copy_from_user((void *) (stream_bufs->addr + copied_size), bufp, size)) { /* Clean up the list and return error code */ retval = -EFAULT; break; } } else if (stream->ops == STREAM_OPS_CAPTURE) { struct snd_sst_user_cap_list *entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { kfree(stream_bufs); return -ENOMEM; } entry->iov_index = index; entry->iov_offset = (unsigned long) bufp - (unsigned long)iovec[index].iov_base; entry->offset = copied_size; entry->size = size; list_add_tail(&entry->node, copy_to_list); } stream->cur_ptr = bufp + size; if (((unsigned long)iovec[index].iov_base + iovec[index].iov_len) < ((unsigned long)iovec[index].iov_base)) { pr_debug("Buffer overflows\n"); kfree(stream_bufs); return -EINVAL; } if (((unsigned long)iovec[index].iov_base + iovec[index].iov_len) == (unsigned long)stream->cur_ptr) { stream->cur_ptr = NULL; stream->sg_index++; } copied_size += size; pr_debug("copied_size - %lx\n", copied_size); if ((copied_size >= mmap_len) || (stream->sg_index == nr_segs)) { add_to_list = 1; } if (add_to_list) { stream_bufs->in_use = false; stream_bufs->size = copied_size; /* locking here */ mutex_lock(&stream->lock); list_add_tail(&stream_bufs->node, &stream->bufs); mutex_unlock(&stream->lock); break; } } return retval; } /* This function copies the captured data returned from SST DSP engine * to the user buffers*/ static int snd_sst_copy_userbuf_capture(struct stream_info *stream, const struct iovec *iovec, struct list_head *copy_to_list) { struct snd_sst_user_cap_list *entry, *_entry; struct sst_stream_bufs *kbufs = NULL, *_kbufs; int retval = 0; /* copy sent buffers */ pr_debug("capture stream copying to user now...\n"); list_for_each_entry_safe(kbufs, _kbufs, &stream->bufs, node) { if (kbufs->in_use == true) { /* copy to user */ list_for_each_entry_safe(entry, _entry, copy_to_list, node) { if (copy_to_user(iovec[entry->iov_index].iov_base + entry->iov_offset, kbufs->addr + entry->offset, entry->size)) { /* Clean up the list and return error */ retval = -EFAULT; break; } list_del(&entry->node); kfree(entry); } } } pr_debug("end of cap copy\n"); return retval; } /* * snd_sst_userbufs_play_cap - constructs the list from user buffers * * @iovec:pointer to iovec structure * @nr_segs:number entries in the iovec structure * @str_id:stream id * @stream:pointer to stream_info structure * * This function will traverse the user list and copy the data to the kernel * space buffers. */ static int snd_sst_userbufs_play_cap(const struct iovec *iovec, unsigned long nr_segs, unsigned int str_id, struct stream_info *stream) { int retval; LIST_HEAD(copy_to_list); retval = snd_sst_fill_kernel_list(stream, iovec, nr_segs, ©_to_list); retval = intel_sst_play_capture(stream, str_id); if (retval < 0) return retval; if (stream->ops == STREAM_OPS_CAPTURE) { retval = snd_sst_copy_userbuf_capture(stream, iovec, ©_to_list); } return retval; } /* This function is common function across read/write for user buffers called from system calls*/ static int intel_sst_read_write(unsigned int str_id, char __user *buf, size_t count) { int retval; struct stream_info *stream; struct iovec iovec; unsigned long nr_segs; retval = sst_validate_strid(str_id); if (retval) return -EINVAL; stream = &sst_drv_ctx->streams[str_id]; if (stream->mmapped == true) { pr_warn("user write and stream is mapped\n"); return -EIO; } if (!count) return -EINVAL; stream->curr_bytes = 0; stream->cumm_bytes = 0; /* copy user buf details */ pr_debug("new buffers %p, copy size %d, status %d\n" , buf, (int) count, (int) stream->status); stream->buf_type = SST_BUF_USER_STATIC; iovec.iov_base = buf; iovec.iov_len = count; nr_segs = 1; do { retval = snd_sst_userbufs_play_cap( &iovec, nr_segs, str_id, stream); if (retval < 0) break; } while (stream->sg_index < nr_segs); stream->sg_index = 0; stream->cur_ptr = NULL; if (retval >= 0) retval = stream->cumm_bytes; pr_debug("end of play/rec bytes = %d!!\n", retval); return retval; } /*** * intel_sst_write - This function is called when user tries to play out data * * @file_ptr:pointer to file * @buf:user buffer to be played out * @count:size of tthe buffer * @offset:offset to start from * * writes the encoded data into DSP */ int intel_sst_write(struct file *file_ptr, const char __user *buf, size_t count, loff_t *offset) { struct ioctl_pvt_data *data = file_ptr->private_data; int str_id = data->str_id; struct stream_info *stream = &sst_drv_ctx->streams[str_id]; pr_debug("called for %d\n", str_id); if (stream->status == STREAM_UN_INIT || stream->status == STREAM_DECODE) { return -EBADRQC; } return intel_sst_read_write(str_id, (char __user *)buf, count); } /* * intel_sst_aio_write - write buffers * * @kiocb:pointer to a structure containing file pointer * @iov:list of user buffer to be played out * @nr_segs:number of entries * @offset:offset to start from * * This function is called when user tries to play out multiple data buffers */ ssize_t intel_sst_aio_write(struct kiocb *kiocb, const struct iovec *iov, unsigned long nr_segs, loff_t offset) { int retval; struct ioctl_pvt_data *data = kiocb->ki_filp->private_data; int str_id = data->str_id; struct stream_info *stream; pr_debug("entry - %ld\n", nr_segs); if (is_sync_kiocb(kiocb) == false) return -EINVAL; pr_debug("called for str_id %d\n", str_id); retval = sst_validate_strid(str_id); if (retval) return -EINVAL; stream = &sst_drv_ctx->streams[str_id]; if (stream->mmapped == true) return -EIO; if (stream->status == STREAM_UN_INIT || stream->status == STREAM_DECODE) { return -EBADRQC; } stream->curr_bytes = 0; stream->cumm_bytes = 0; pr_debug("new segs %ld, offset %d, status %d\n" , nr_segs, (int) offset, (int) stream->status); stream->buf_type = SST_BUF_USER_STATIC; do { retval = snd_sst_userbufs_play_cap(iov, nr_segs, str_id, stream); if (retval < 0) break; } while (stream->sg_index < nr_segs); stream->sg_index = 0; stream->cur_ptr = NULL; if (retval >= 0) retval = stream->cumm_bytes; pr_debug("end of play/rec bytes = %d!!\n", retval); return retval; } /* * intel_sst_read - read the encoded data * * @file_ptr: pointer to file * @buf: user buffer to be filled with captured data * @count: size of tthe buffer * @offset: offset to start from * * This function is called when user tries to capture data */ int intel_sst_read(struct file *file_ptr, char __user *buf, size_t count, loff_t *offset) { struct ioctl_pvt_data *data = file_ptr->private_data; int str_id = data->str_id; struct stream_info *stream = &sst_drv_ctx->streams[str_id]; pr_debug("called for %d\n", str_id); if (stream->status == STREAM_UN_INIT || stream->status == STREAM_DECODE) return -EBADRQC; return intel_sst_read_write(str_id, buf, count); } /* * intel_sst_aio_read - aio read * * @kiocb: pointer to a structure containing file pointer * @iov: list of user buffer to be filled with captured * @nr_segs: number of entries * @offset: offset to start from * * This function is called when user tries to capture out multiple data buffers */ ssize_t intel_sst_aio_read(struct kiocb *kiocb, const struct iovec *iov, unsigned long nr_segs, loff_t offset) { int retval; struct ioctl_pvt_data *data = kiocb->ki_filp->private_data; int str_id = data->str_id; struct stream_info *stream; pr_debug("entry - %ld\n", nr_segs); if (is_sync_kiocb(kiocb) == false) { pr_debug("aio_read from user space is not allowed\n"); return -EINVAL; } pr_debug("called for str_id %d\n", str_id); retval = sst_validate_strid(str_id); if (retval) return -EINVAL; stream = &sst_drv_ctx->streams[str_id]; if (stream->mmapped == true) return -EIO; if (stream->status == STREAM_UN_INIT || stream->status == STREAM_DECODE) return -EBADRQC; stream->curr_bytes = 0; stream->cumm_bytes = 0; pr_debug("new segs %ld, offset %d, status %d\n" , nr_segs, (int) offset, (int) stream->status); stream->buf_type = SST_BUF_USER_STATIC; do { retval = snd_sst_userbufs_play_cap(iov, nr_segs, str_id, stream); if (retval < 0) break; } while (stream->sg_index < nr_segs); stream->sg_index = 0; stream->cur_ptr = NULL; if (retval >= 0) retval = stream->cumm_bytes; pr_debug("end of play/rec bytes = %d!!\n", retval); return retval; } /* sst_print_stream_params - prints the stream parameters (debug fn)*/ static void sst_print_stream_params(struct snd_sst_get_stream_params *get_prm) { pr_debug("codec params:result = %d\n", get_prm->codec_params.result); pr_debug("codec params:stream = %d\n", get_prm->codec_params.stream_id); pr_debug("codec params:codec = %d\n", get_prm->codec_params.codec); pr_debug("codec params:ops = %d\n", get_prm->codec_params.ops); pr_debug("codec params:stream_type = %d\n", get_prm->codec_params.stream_type); pr_debug("pcmparams:sfreq = %d\n", get_prm->pcm_params.sfreq); pr_debug("pcmparams:num_chan = %d\n", get_prm->pcm_params.num_chan); pr_debug("pcmparams:pcm_wd_sz = %d\n", get_prm->pcm_params.pcm_wd_sz); return; } /** * sst_create_algo_ipc - create ipc msg for algorithm parameters * * @algo_params: Algorithm parameters * @msg: post msg pointer * * This function is called to create ipc msg */ int sst_create_algo_ipc(struct snd_ppp_params *algo_params, struct ipc_post **msg) { if (sst_create_large_msg(msg)) return -ENOMEM; sst_fill_header(&(*msg)->header, IPC_IA_ALG_PARAMS, 1, algo_params->str_id); (*msg)->header.part.data = sizeof(u32) + sizeof(*algo_params) + algo_params->size; memcpy((*msg)->mailbox_data, &(*msg)->header, sizeof(u32)); memcpy((*msg)->mailbox_data + sizeof(u32), algo_params, sizeof(*algo_params)); return 0; } /** * sst_send_algo_ipc - send ipc msg for algorithm parameters * * @msg: post msg pointer * * This function is called to send ipc msg */ int sst_send_algo_ipc(struct ipc_post **msg) { sst_drv_ctx->ppp_params_blk.condition = false; sst_drv_ctx->ppp_params_blk.ret_code = 0; sst_drv_ctx->ppp_params_blk.on = true; sst_drv_ctx->ppp_params_blk.data = NULL; spin_lock(&sst_drv_ctx->list_spin_lock); list_add_tail(&(*msg)->node, &sst_drv_ctx->ipc_dispatch_list); spin_unlock(&sst_drv_ctx->list_spin_lock); sst_post_message(&sst_drv_ctx->ipc_post_msg_wq); return sst_wait_interruptible_timeout(sst_drv_ctx, &sst_drv_ctx->ppp_params_blk, SST_BLOCK_TIMEOUT); } /** * intel_sst_ioctl_dsp - recieves the device ioctl's * * @cmd:Ioctl cmd * @arg:data * * This function is called when a user space component * sends a DSP Ioctl to SST driver */ long intel_sst_ioctl_dsp(unsigned int cmd, unsigned long arg) { int retval = 0; struct snd_ppp_params algo_params; struct snd_ppp_params *algo_params_copied; struct ipc_post *msg; switch (_IOC_NR(cmd)) { case _IOC_NR(SNDRV_SST_SET_ALGO): if (copy_from_user(&algo_params, (void __user *)arg, sizeof(algo_params))) return -EFAULT; if (algo_params.size > SST_MAILBOX_SIZE) return -EMSGSIZE; pr_debug("Algo ID %d Str id %d Enable %d Size %d\n", algo_params.algo_id, algo_params.str_id, algo_params.enable, algo_params.size); retval = sst_create_algo_ipc(&algo_params, &msg); if (retval) break; algo_params.reserved = 0; if (copy_from_user(msg->mailbox_data + sizeof(algo_params), algo_params.params, algo_params.size)) return -EFAULT; retval = sst_send_algo_ipc(&msg); if (retval) { pr_debug("Error in sst_set_algo = %d\n", retval); retval = -EIO; } break; case _IOC_NR(SNDRV_SST_GET_ALGO): if (copy_from_user(&algo_params, (void __user *)arg, sizeof(algo_params))) return -EFAULT; pr_debug("Algo ID %d Str id %d Enable %d Size %d\n", algo_params.algo_id, algo_params.str_id, algo_params.enable, algo_params.size); retval = sst_create_algo_ipc(&algo_params, &msg); if (retval) break; algo_params.reserved = 1; retval = sst_send_algo_ipc(&msg); if (retval) { pr_debug("Error in sst_get_algo = %d\n", retval); retval = -EIO; break; } algo_params_copied = (struct snd_ppp_params *) sst_drv_ctx->ppp_params_blk.data; if (algo_params_copied->size > algo_params.size) { pr_debug("mem insufficient to copy\n"); retval = -EMSGSIZE; goto free_mem; } else { char __user *tmp; if (copy_to_user(algo_params.params, algo_params_copied->params, algo_params_copied->size)) { retval = -EFAULT; goto free_mem; } tmp = (char __user *)arg + offsetof( struct snd_ppp_params, size); if (copy_to_user(tmp, &algo_params_copied->size, sizeof(__u32))) { retval = -EFAULT; goto free_mem; } } free_mem: kfree(algo_params_copied->params); kfree(algo_params_copied); break; } return retval; } /** * intel_sst_ioctl - receives the device ioctl's * @file_ptr:pointer to file * @cmd:Ioctl cmd * @arg:data * * This function is called by OS when a user space component * sends an Ioctl to SST driver */ long intel_sst_ioctl(struct file *file_ptr, unsigned int cmd, unsigned long arg) { int retval = 0; struct ioctl_pvt_data *data = NULL; int str_id = 0, minor = 0; data = file_ptr->private_data; if (data) { minor = 0; str_id = data->str_id; } else minor = 1; if (sst_drv_ctx->sst_state != SST_FW_RUNNING) return -EBUSY; switch (_IOC_NR(cmd)) { case _IOC_NR(SNDRV_SST_STREAM_PAUSE): pr_debug("IOCTL_PAUSE received for %d!\n", str_id); if (minor != STREAM_MODULE) { retval = -EBADRQC; break; } retval = sst_pause_stream(str_id); break; case _IOC_NR(SNDRV_SST_STREAM_RESUME): pr_debug("SNDRV_SST_IOCTL_RESUME received!\n"); if (minor != STREAM_MODULE) { retval = -EBADRQC; break; } retval = sst_resume_stream(str_id); break; case _IOC_NR(SNDRV_SST_STREAM_SET_PARAMS): { struct snd_sst_params str_param; pr_debug("IOCTL_SET_PARAMS received!\n"); if (minor != STREAM_MODULE) { retval = -EBADRQC; break; } if (copy_from_user(&str_param, (void __user *)arg, sizeof(str_param))) { retval = -EFAULT; break; } if (!str_id) { retval = sst_get_stream(&str_param); if (retval > 0) { struct stream_info *str_info; char __user *dest; sst_drv_ctx->stream_cnt++; data->str_id = retval; str_info = &sst_drv_ctx->streams[retval]; str_info->src = SST_DRV; dest = (char __user *)arg + offsetof(struct snd_sst_params, stream_id); retval = copy_to_user(dest, &retval, sizeof(__u32)); if (retval) retval = -EFAULT; } else { if (retval == -SST_ERR_INVALID_PARAMS) retval = -EINVAL; } } else { pr_debug("SET_STREAM_PARAMS received!\n"); /* allocated set params only */ retval = sst_set_stream_param(str_id, &str_param); /* Block the call for reply */ if (!retval) { int sfreq = 0, word_size = 0, num_channel = 0; sfreq = str_param.sparams.uc.pcm_params.sfreq; word_size = str_param.sparams.uc.pcm_params.pcm_wd_sz; num_channel = str_param.sparams.uc.pcm_params.num_chan; if (str_param.ops == STREAM_OPS_CAPTURE) { sst_drv_ctx->scard_ops->\ set_pcm_audio_params(sfreq, word_size, num_channel); } } } break; } case _IOC_NR(SNDRV_SST_SET_VOL): { struct snd_sst_vol set_vol; if (copy_from_user(&set_vol, (void __user *)arg, sizeof(set_vol))) { pr_debug("copy failed\n"); retval = -EFAULT; break; } pr_debug("SET_VOLUME recieved for %d!\n", set_vol.stream_id); if (minor == STREAM_MODULE && set_vol.stream_id == 0) { pr_debug("invalid operation!\n"); retval = -EPERM; break; } retval = sst_set_vol(&set_vol); break; } case _IOC_NR(SNDRV_SST_GET_VOL): { struct snd_sst_vol get_vol; if (copy_from_user(&get_vol, (void __user *)arg, sizeof(get_vol))) { retval = -EFAULT; break; } pr_debug("IOCTL_GET_VOLUME recieved for stream = %d!\n", get_vol.stream_id); if (minor == STREAM_MODULE && get_vol.stream_id == 0) { pr_debug("invalid operation!\n"); retval = -EPERM; break; } retval = sst_get_vol(&get_vol); if (retval) { retval = -EIO; break; } pr_debug("id:%d\n, vol:%d, ramp_dur:%d, ramp_type:%d\n", get_vol.stream_id, get_vol.volume, get_vol.ramp_duration, get_vol.ramp_type); if (copy_to_user((struct snd_sst_vol __user *)arg, &get_vol, sizeof(get_vol))) { retval = -EFAULT; break; } /*sst_print_get_vol_info(str_id, &get_vol);*/ break; } case _IOC_NR(SNDRV_SST_MUTE): { struct snd_sst_mute set_mute; if (copy_from_user(&set_mute, (void __user *)arg, sizeof(set_mute))) { retval = -EFAULT; break; } pr_debug("SNDRV_SST_SET_VOLUME recieved for %d!\n", set_mute.stream_id); if (minor == STREAM_MODULE && set_mute.stream_id == 0) { retval = -EPERM; break; } retval = sst_set_mute(&set_mute); break; } case _IOC_NR(SNDRV_SST_STREAM_GET_PARAMS): { struct snd_sst_get_stream_params get_params; pr_debug("IOCTL_GET_PARAMS received!\n"); if (minor != 0) { retval = -EBADRQC; break; } retval = sst_get_stream_params(str_id, &get_params); if (retval) { retval = -EIO; break; } if (copy_to_user((struct snd_sst_get_stream_params __user *)arg, &get_params, sizeof(get_params))) { retval = -EFAULT; break; } sst_print_stream_params(&get_params); break; } case _IOC_NR(SNDRV_SST_MMAP_PLAY): case _IOC_NR(SNDRV_SST_MMAP_CAPTURE): { struct snd_sst_mmap_buffs mmap_buf; pr_debug("SNDRV_SST_MMAP_PLAY/CAPTURE recieved!\n"); if (minor != STREAM_MODULE) { retval = -EBADRQC; break; } if (copy_from_user(&mmap_buf, (void __user *)arg, sizeof(mmap_buf))) { retval = -EFAULT; break; } retval = intel_sst_mmap_play_capture(str_id, &mmap_buf); break; } case _IOC_NR(SNDRV_SST_STREAM_DROP): pr_debug("SNDRV_SST_IOCTL_DROP received!\n"); if (minor != STREAM_MODULE) { retval = -EINVAL; break; } retval = sst_drop_stream(str_id); break; case _IOC_NR(SNDRV_SST_STREAM_GET_TSTAMP): { struct snd_sst_tstamp tstamp = {0}; unsigned long long time, freq, mod; pr_debug("SNDRV_SST_STREAM_GET_TSTAMP received!\n"); if (minor != STREAM_MODULE) { retval = -EBADRQC; break; } memcpy_fromio(&tstamp, sst_drv_ctx->mailbox + SST_TIME_STAMP + str_id * sizeof(tstamp), sizeof(tstamp)); time = tstamp.samples_rendered; freq = (unsigned long long) tstamp.sampling_frequency; time = time * 1000; /* converting it to ms */ mod = do_div(time, freq); if (copy_to_user((void __user *)arg, &time, sizeof(unsigned long long))) retval = -EFAULT; break; } case _IOC_NR(SNDRV_SST_STREAM_START):{ struct stream_info *stream; pr_debug("SNDRV_SST_STREAM_START received!\n"); if (minor != STREAM_MODULE) { retval = -EINVAL; break; } retval = sst_validate_strid(str_id); if (retval) break; stream = &sst_drv_ctx->streams[str_id]; mutex_lock(&stream->lock); if (stream->status == STREAM_INIT && stream->need_draining != true) { stream->prev = stream->status; stream->status = STREAM_RUNNING; if (stream->ops == STREAM_OPS_PLAYBACK || stream->ops == STREAM_OPS_PLAYBACK_DRM) { retval = sst_play_frame(str_id); } else if (stream->ops == STREAM_OPS_CAPTURE) retval = sst_capture_frame(str_id); else { retval = -EINVAL; mutex_unlock(&stream->lock); break; } if (retval < 0) { stream->status = STREAM_INIT; mutex_unlock(&stream->lock); break; } } else { retval = -EINVAL; } mutex_unlock(&stream->lock); break; } case _IOC_NR(SNDRV_SST_SET_TARGET_DEVICE): { struct snd_sst_target_device target_device; pr_debug("SET_TARGET_DEVICE recieved!\n"); if (copy_from_user(&target_device, (void __user *)arg, sizeof(target_device))) { retval = -EFAULT; break; } if (minor != AM_MODULE) { retval = -EBADRQC; break; } retval = sst_target_device_select(&target_device); break; } case _IOC_NR(SNDRV_SST_DRIVER_INFO): { struct snd_sst_driver_info info; pr_debug("SNDRV_SST_DRIVER_INFO recived\n"); info.version = SST_VERSION_NUM; /* hard coding, shud get sumhow later */ info.active_pcm_streams = sst_drv_ctx->stream_cnt - sst_drv_ctx->encoded_cnt; info.active_enc_streams = sst_drv_ctx->encoded_cnt; info.max_pcm_streams = MAX_ACTIVE_STREAM - MAX_ENC_STREAM; info.max_enc_streams = MAX_ENC_STREAM; info.buf_per_stream = sst_drv_ctx->mmap_len; if (copy_to_user((void __user *)arg, &info, sizeof(info))) retval = -EFAULT; break; } case _IOC_NR(SNDRV_SST_STREAM_DECODE): { struct snd_sst_dbufs param; struct snd_sst_dbufs dbufs_local; struct snd_sst_buffs ibufs, obufs; struct snd_sst_buff_entry *ibuf_tmp, *obuf_tmp; char __user *dest; pr_debug("SNDRV_SST_STREAM_DECODE received\n"); if (minor != STREAM_MODULE) { retval = -EBADRQC; break; } if (copy_from_user(¶m, (void __user *)arg, sizeof(param))) { retval = -EFAULT; break; } dbufs_local.input_bytes_consumed = param.input_bytes_consumed; dbufs_local.output_bytes_produced = param.output_bytes_produced; if (copy_from_user(&ibufs, (void __user *)param.ibufs, sizeof(ibufs))) { retval = -EFAULT; break; } if (copy_from_user(&obufs, (void __user *)param.obufs, sizeof(obufs))) { retval = -EFAULT; break; } ibuf_tmp = kcalloc(ibufs.entries, sizeof(*ibuf_tmp), GFP_KERNEL); obuf_tmp = kcalloc(obufs.entries, sizeof(*obuf_tmp), GFP_KERNEL); if (!ibuf_tmp || !obuf_tmp) { retval = -ENOMEM; goto free_iobufs; } if (copy_from_user(ibuf_tmp, (void __user *)ibufs.buff_entry, ibufs.entries * sizeof(*ibuf_tmp))) { retval = -EFAULT; goto free_iobufs; } ibufs.buff_entry = ibuf_tmp; dbufs_local.ibufs = &ibufs; if (copy_from_user(obuf_tmp, (void __user *)obufs.buff_entry, obufs.entries * sizeof(*obuf_tmp))) { retval = -EFAULT; goto free_iobufs; } obufs.buff_entry = obuf_tmp; dbufs_local.obufs = &obufs; retval = sst_decode(str_id, &dbufs_local); if (retval) { retval = -EAGAIN; goto free_iobufs; } dest = (char __user *)arg + offsetof(struct snd_sst_dbufs, input_bytes_consumed); if (copy_to_user(dest, &dbufs_local.input_bytes_consumed, sizeof(unsigned long long))) { retval = -EFAULT; goto free_iobufs; } dest = (char __user *)arg + offsetof(struct snd_sst_dbufs, input_bytes_consumed); if (copy_to_user(dest, &dbufs_local.output_bytes_produced, sizeof(unsigned long long))) { retval = -EFAULT; goto free_iobufs; } free_iobufs: kfree(ibuf_tmp); kfree(obuf_tmp); break; } case _IOC_NR(SNDRV_SST_STREAM_DRAIN): pr_debug("SNDRV_SST_STREAM_DRAIN received\n"); if (minor != STREAM_MODULE) { retval = -EINVAL; break; } retval = sst_drain_stream(str_id); break; case _IOC_NR(SNDRV_SST_STREAM_BYTES_DECODED): { unsigned long long __user *bytes = (unsigned long long __user *)arg; struct snd_sst_tstamp tstamp = {0}; pr_debug("STREAM_BYTES_DECODED received!\n"); if (minor != STREAM_MODULE) { retval = -EINVAL; break; } memcpy_fromio(&tstamp, sst_drv_ctx->mailbox + SST_TIME_STAMP + str_id * sizeof(tstamp), sizeof(tstamp)); if (copy_to_user(bytes, &tstamp.bytes_processed, sizeof(*bytes))) retval = -EFAULT; break; } case _IOC_NR(SNDRV_SST_FW_INFO): { struct snd_sst_fw_info *fw_info; pr_debug("SNDRV_SST_FW_INFO received\n"); fw_info = kzalloc(sizeof(*fw_info), GFP_ATOMIC); if (!fw_info) { retval = -ENOMEM; break; } retval = sst_get_fw_info(fw_info); if (retval) { retval = -EIO; kfree(fw_info); break; } if (copy_to_user((struct snd_sst_dbufs __user *)arg, fw_info, sizeof(*fw_info))) { kfree(fw_info); retval = -EFAULT; break; } /*sst_print_fw_info(fw_info);*/ kfree(fw_info); break; } case _IOC_NR(SNDRV_SST_GET_ALGO): case _IOC_NR(SNDRV_SST_SET_ALGO): if (minor != AM_MODULE) { retval = -EBADRQC; break; } retval = intel_sst_ioctl_dsp(cmd, arg); break; default: retval = -EINVAL; } pr_debug("intel_sst_ioctl:complete ret code = %d\n", retval); return retval; }