/* * DSPG DBMDX codec driver character device interface * * Copyright (C) 2014 DSP Group * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. */ #include #include #include #include #include #include #include #include "dbmdx-interface.h" static struct dbmdx_private *dbmdx_p; static atomic_t cdev_opened = ATOMIC_INIT(0); /* Access to the audio buffer is controlled through "audio_owner". Either the * character device or the ALSA-capture device can be opened. */ static int dbmdx_record_open(struct inode *inode, struct file *file) { file->private_data = dbmdx_p; if (!atomic_add_unless(&cdev_opened, 1, 1)) return -EBUSY; return 0; } static int dbmdx_record_release(struct inode *inode, struct file *file) { dbmdx_p->lock(dbmdx_p); dbmdx_p->va_flags.buffering = 0; dbmdx_p->unlock(dbmdx_p); flush_work(&dbmdx_p->sv_work); atomic_dec(&cdev_opened); return 0; } /* * read out of the kfifo as much as was requested or if requested more * as much as is in the FIFO */ static ssize_t dbmdx_record_read(struct file *file, char __user *buf, size_t count_want, loff_t *f_pos) { struct dbmdx_private *p = (struct dbmdx_private *)file->private_data; size_t not_copied; ssize_t to_copy = count_want; int avail; unsigned int copied; int ret; dev_dbg(p->dbmdx_dev, "%s: count_want:%zu f_pos:%lld\n", __func__, count_want, *f_pos); avail = kfifo_len(&p->detection_samples_kfifo); if (avail == 0) return 0; if (count_want > avail) to_copy = avail; ret = kfifo_to_user(&p->detection_samples_kfifo, buf, to_copy, &copied); if (ret) return -EIO; not_copied = count_want - copied; *f_pos = *f_pos + (count_want - not_copied); return count_want - not_copied; } static const struct file_operations dbmdx_cdev_fops = { .owner = THIS_MODULE, .open = dbmdx_record_open, .release = dbmdx_record_release, .read = dbmdx_record_read, }; /* * read out of the kfifo as much as was requested and block until all * data is available or a timeout occurs */ static ssize_t dbmdx_record_read_blocking(struct file *file, char __user *buf, size_t count_want, loff_t *f_pos) { struct dbmdx_private *p = (struct dbmdx_private *)file->private_data; size_t not_copied; ssize_t to_copy = count_want; int avail; unsigned int copied, total_copied = 0; int ret; unsigned long timeout = jiffies + msecs_to_jiffies(500); dev_dbg(p->dbmdx_dev, "%s: count_want:%zu f_pos:%lld\n", __func__, count_want, *f_pos); avail = kfifo_len(&p->detection_samples_kfifo); while ((total_copied < count_want) && time_before(jiffies, timeout) && avail) { to_copy = avail; if (count_want - total_copied < avail) to_copy = count_want - total_copied; ret = kfifo_to_user(&p->detection_samples_kfifo, buf + total_copied, to_copy, &copied); if (ret) return -EIO; total_copied += copied; avail = kfifo_len(&p->detection_samples_kfifo); if (avail == 0 && p->va_flags.buffering) usleep_range(100000, 110000); } if (avail && (total_copied < count_want)) dev_err(p->dev, "dbmdx: timeout during reading\n"); not_copied = count_want - total_copied; *f_pos = *f_pos + (count_want - not_copied); return count_want - not_copied; } static const struct file_operations dbmdx_cdev_block_fops = { .owner = THIS_MODULE, .open = dbmdx_record_open, .release = dbmdx_record_release, .read = dbmdx_record_read_blocking, }; int dbmdx_register_cdev(struct dbmdx_private *p) { int ret; dbmdx_p = p; ret = alloc_chrdev_region(&p->record_chrdev, 0, 2, "dbmdx"); if (ret) { dev_err(p->dbmdx_dev, "failed to allocate character device\n"); return -EINVAL; } cdev_init(&p->record_cdev, &dbmdx_cdev_fops); cdev_init(&p->record_cdev_block, &dbmdx_cdev_block_fops); p->record_cdev.owner = THIS_MODULE; p->record_cdev_block.owner = THIS_MODULE; ret = cdev_add(&p->record_cdev, p->record_chrdev, 1); if (ret) { dev_err(p->dbmdx_dev, "failed to add character device\n"); unregister_chrdev_region(p->record_chrdev, 1); return -EINVAL; } ret = cdev_add(&p->record_cdev_block, p->record_chrdev + 1, 1); if (ret) { dev_err(p->dbmdx_dev, "failed to add blocking character device\n"); unregister_chrdev_region(p->record_chrdev, 1); return -EINVAL; } p->record_dev = device_create(p->ns_class, &platform_bus, MKDEV(MAJOR(p->record_chrdev), 0), p, "dbmdx-%d", 0); if (IS_ERR(p->record_dev)) { dev_err(p->dev, "could not create device\n"); unregister_chrdev_region(p->record_chrdev, 1); cdev_del(&p->record_cdev); return -EINVAL; } p->record_dev_block = device_create(p->ns_class, &platform_bus, MKDEV(MAJOR(p->record_chrdev), 1), p, "dbmdx-%d", 1); if (IS_ERR(p->record_dev_block)) { dev_err(p->dev, "could not create device\n"); unregister_chrdev_region(p->record_chrdev, 1); cdev_del(&p->record_cdev_block); return -EINVAL; } return 0; } EXPORT_SYMBOL(dbmdx_register_cdev); void dbmdx_deregister_cdev(struct dbmdx_private *p) { if (p->record_dev) { device_unregister(p->record_dev); cdev_del(&p->record_cdev); } if (p->record_dev_block) { device_unregister(p->record_dev_block); cdev_del(&p->record_cdev_block); } unregister_chrdev_region(p->record_chrdev, 2); dbmdx_p = NULL; } EXPORT_SYMBOL(dbmdx_deregister_cdev);