| /* | |
| * dmxdev.c - DVB demultiplexer device | |
| * | |
| * Copyright (C) 2000 Ralph Metzler & Marcus Metzler | |
| * for convergence integrated media GmbH | |
| * | |
| * This program 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 General Public License for more details. | |
| * | |
| */ | |
| #define pr_fmt(fmt) "dmxdev: " fmt | |
| #include <linux/sched.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/slab.h> | |
| #include <linux/vmalloc.h> | |
| #include <linux/module.h> | |
| #include <linux/poll.h> | |
| #include <linux/ioctl.h> | |
| #include <linux/wait.h> | |
| #include <linux/uaccess.h> | |
| #include <media/dmxdev.h> | |
| #include <media/dvb_vb2.h> | |
| static int debug; | |
| module_param(debug, int, 0644); | |
| MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); | |
| #define dprintk(fmt, arg...) do { \ | |
| if (debug) \ | |
| printk(KERN_DEBUG pr_fmt("%s: " fmt), \ | |
| __func__, ##arg); \ | |
| } while (0) | |
| static int dvb_dmxdev_buffer_write(struct dvb_ringbuffer *buf, | |
| const u8 *src, size_t len) | |
| { | |
| ssize_t free; | |
| if (!len) | |
| return 0; | |
| if (!buf->data) | |
| return 0; | |
| free = dvb_ringbuffer_free(buf); | |
| if (len > free) { | |
| dprintk("buffer overflow\n"); | |
| return -EOVERFLOW; | |
| } | |
| return dvb_ringbuffer_write(buf, src, len); | |
| } | |
| static ssize_t dvb_dmxdev_buffer_read(struct dvb_ringbuffer *src, | |
| int non_blocking, char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| size_t todo; | |
| ssize_t avail; | |
| ssize_t ret = 0; | |
| if (!src->data) | |
| return 0; | |
| if (src->error) { | |
| ret = src->error; | |
| dvb_ringbuffer_flush(src); | |
| return ret; | |
| } | |
| for (todo = count; todo > 0; todo -= ret) { | |
| if (non_blocking && dvb_ringbuffer_empty(src)) { | |
| ret = -EWOULDBLOCK; | |
| break; | |
| } | |
| ret = wait_event_interruptible(src->queue, | |
| !dvb_ringbuffer_empty(src) || | |
| (src->error != 0)); | |
| if (ret < 0) | |
| break; | |
| if (src->error) { | |
| ret = src->error; | |
| dvb_ringbuffer_flush(src); | |
| break; | |
| } | |
| avail = dvb_ringbuffer_avail(src); | |
| if (avail > todo) | |
| avail = todo; | |
| ret = dvb_ringbuffer_read_user(src, buf, avail); | |
| if (ret < 0) | |
| break; | |
| buf += ret; | |
| } | |
| return (count - todo) ? (count - todo) : ret; | |
| } | |
| static struct dmx_frontend *get_fe(struct dmx_demux *demux, int type) | |
| { | |
| struct list_head *head, *pos; | |
| head = demux->get_frontends(demux); | |
| if (!head) | |
| return NULL; | |
| list_for_each(pos, head) | |
| if (DMX_FE_ENTRY(pos)->source == type) | |
| return DMX_FE_ENTRY(pos); | |
| return NULL; | |
| } | |
| static int dvb_dvr_open(struct inode *inode, struct file *file) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| struct dmx_frontend *front; | |
| bool need_ringbuffer = false; | |
| dprintk("%s\n", __func__); | |
| if (mutex_lock_interruptible(&dmxdev->mutex)) | |
| return -ERESTARTSYS; | |
| if (dmxdev->exit) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ENODEV; | |
| } | |
| dmxdev->may_do_mmap = 0; | |
| /* | |
| * The logic here is a little tricky due to the ifdef. | |
| * | |
| * The ringbuffer is used for both read and mmap. | |
| * | |
| * It is not needed, however, on two situations: | |
| * - Write devices (access with O_WRONLY); | |
| * - For duplex device nodes, opened with O_RDWR. | |
| */ | |
| if ((file->f_flags & O_ACCMODE) == O_RDONLY) | |
| need_ringbuffer = true; | |
| else if ((file->f_flags & O_ACCMODE) == O_RDWR) { | |
| if (!(dmxdev->capabilities & DMXDEV_CAP_DUPLEX)) { | |
| #ifdef CONFIG_DVB_MMAP | |
| dmxdev->may_do_mmap = 1; | |
| need_ringbuffer = true; | |
| #else | |
| mutex_unlock(&dmxdev->mutex); | |
| return -EOPNOTSUPP; | |
| #endif | |
| } | |
| } | |
| if (need_ringbuffer) { | |
| void *mem; | |
| if (!dvbdev->readers) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -EBUSY; | |
| } | |
| mem = vmalloc(DVR_BUFFER_SIZE); | |
| if (!mem) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ENOMEM; | |
| } | |
| dvb_ringbuffer_init(&dmxdev->dvr_buffer, mem, DVR_BUFFER_SIZE); | |
| if (dmxdev->may_do_mmap) | |
| dvb_vb2_init(&dmxdev->dvr_vb2_ctx, "dvr", | |
| file->f_flags & O_NONBLOCK); | |
| dvbdev->readers--; | |
| } | |
| if ((file->f_flags & O_ACCMODE) == O_WRONLY) { | |
| dmxdev->dvr_orig_fe = dmxdev->demux->frontend; | |
| if (!dmxdev->demux->write) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -EOPNOTSUPP; | |
| } | |
| front = get_fe(dmxdev->demux, DMX_MEMORY_FE); | |
| if (!front) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -EINVAL; | |
| } | |
| dmxdev->demux->disconnect_frontend(dmxdev->demux); | |
| dmxdev->demux->connect_frontend(dmxdev->demux, front); | |
| } | |
| dvbdev->users++; | |
| mutex_unlock(&dmxdev->mutex); | |
| return 0; | |
| } | |
| static int dvb_dvr_release(struct inode *inode, struct file *file) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| mutex_lock(&dmxdev->mutex); | |
| if ((file->f_flags & O_ACCMODE) == O_WRONLY) { | |
| dmxdev->demux->disconnect_frontend(dmxdev->demux); | |
| dmxdev->demux->connect_frontend(dmxdev->demux, | |
| dmxdev->dvr_orig_fe); | |
| } | |
| if (((file->f_flags & O_ACCMODE) == O_RDONLY) || | |
| dmxdev->may_do_mmap) { | |
| if (dmxdev->may_do_mmap) { | |
| if (dvb_vb2_is_streaming(&dmxdev->dvr_vb2_ctx)) | |
| dvb_vb2_stream_off(&dmxdev->dvr_vb2_ctx); | |
| dvb_vb2_release(&dmxdev->dvr_vb2_ctx); | |
| } | |
| dvbdev->readers++; | |
| if (dmxdev->dvr_buffer.data) { | |
| void *mem = dmxdev->dvr_buffer.data; | |
| /*memory barrier*/ | |
| mb(); | |
| spin_lock_irq(&dmxdev->lock); | |
| dmxdev->dvr_buffer.data = NULL; | |
| spin_unlock_irq(&dmxdev->lock); | |
| vfree(mem); | |
| } | |
| } | |
| /* TODO */ | |
| dvbdev->users--; | |
| if (dvbdev->users == 1 && dmxdev->exit == 1) { | |
| mutex_unlock(&dmxdev->mutex); | |
| wake_up(&dvbdev->wait_queue); | |
| } else | |
| mutex_unlock(&dmxdev->mutex); | |
| return 0; | |
| } | |
| static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| int ret; | |
| if (!dmxdev->demux->write) | |
| return -EOPNOTSUPP; | |
| if ((file->f_flags & O_ACCMODE) != O_WRONLY) | |
| return -EINVAL; | |
| if (mutex_lock_interruptible(&dmxdev->mutex)) | |
| return -ERESTARTSYS; | |
| if (dmxdev->exit) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ENODEV; | |
| } | |
| ret = dmxdev->demux->write(dmxdev->demux, buf, count); | |
| mutex_unlock(&dmxdev->mutex); | |
| return ret; | |
| } | |
| static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, | |
| loff_t *ppos) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| if (dmxdev->exit) | |
| return -ENODEV; | |
| return dvb_dmxdev_buffer_read(&dmxdev->dvr_buffer, | |
| file->f_flags & O_NONBLOCK, | |
| buf, count, ppos); | |
| } | |
| static int dvb_dvr_set_buffer_size(struct dmxdev *dmxdev, | |
| unsigned long size) | |
| { | |
| struct dvb_ringbuffer *buf = &dmxdev->dvr_buffer; | |
| void *newmem; | |
| void *oldmem; | |
| dprintk("%s\n", __func__); | |
| if (buf->size == size) | |
| return 0; | |
| if (!size) | |
| return -EINVAL; | |
| newmem = vmalloc(size); | |
| if (!newmem) | |
| return -ENOMEM; | |
| oldmem = buf->data; | |
| spin_lock_irq(&dmxdev->lock); | |
| buf->data = newmem; | |
| buf->size = size; | |
| /* reset and not flush in case the buffer shrinks */ | |
| dvb_ringbuffer_reset(buf); | |
| spin_unlock_irq(&dmxdev->lock); | |
| vfree(oldmem); | |
| return 0; | |
| } | |
| static inline void dvb_dmxdev_filter_state_set(struct dmxdev_filter | |
| *dmxdevfilter, int state) | |
| { | |
| spin_lock_irq(&dmxdevfilter->dev->lock); | |
| dmxdevfilter->state = state; | |
| spin_unlock_irq(&dmxdevfilter->dev->lock); | |
| } | |
| static int dvb_dmxdev_set_buffer_size(struct dmxdev_filter *dmxdevfilter, | |
| unsigned long size) | |
| { | |
| struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; | |
| void *newmem; | |
| void *oldmem; | |
| if (buf->size == size) | |
| return 0; | |
| if (!size) | |
| return -EINVAL; | |
| if (dmxdevfilter->state >= DMXDEV_STATE_GO) | |
| return -EBUSY; | |
| newmem = vmalloc(size); | |
| if (!newmem) | |
| return -ENOMEM; | |
| oldmem = buf->data; | |
| spin_lock_irq(&dmxdevfilter->dev->lock); | |
| buf->data = newmem; | |
| buf->size = size; | |
| /* reset and not flush in case the buffer shrinks */ | |
| dvb_ringbuffer_reset(buf); | |
| spin_unlock_irq(&dmxdevfilter->dev->lock); | |
| vfree(oldmem); | |
| return 0; | |
| } | |
| static void dvb_dmxdev_filter_timeout(struct timer_list *t) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = from_timer(dmxdevfilter, t, timer); | |
| dmxdevfilter->buffer.error = -ETIMEDOUT; | |
| spin_lock_irq(&dmxdevfilter->dev->lock); | |
| dmxdevfilter->state = DMXDEV_STATE_TIMEDOUT; | |
| spin_unlock_irq(&dmxdevfilter->dev->lock); | |
| wake_up(&dmxdevfilter->buffer.queue); | |
| } | |
| static void dvb_dmxdev_filter_timer(struct dmxdev_filter *dmxdevfilter) | |
| { | |
| struct dmx_sct_filter_params *para = &dmxdevfilter->params.sec; | |
| del_timer(&dmxdevfilter->timer); | |
| if (para->timeout) { | |
| dmxdevfilter->timer.expires = | |
| jiffies + 1 + (HZ / 2 + HZ * para->timeout) / 1000; | |
| add_timer(&dmxdevfilter->timer); | |
| } | |
| } | |
| static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, | |
| const u8 *buffer2, size_t buffer2_len, | |
| struct dmx_section_filter *filter, | |
| u32 *buffer_flags) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = filter->priv; | |
| int ret; | |
| if (!dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx) && | |
| dmxdevfilter->buffer.error) { | |
| wake_up(&dmxdevfilter->buffer.queue); | |
| return 0; | |
| } | |
| spin_lock(&dmxdevfilter->dev->lock); | |
| if (dmxdevfilter->state != DMXDEV_STATE_GO) { | |
| spin_unlock(&dmxdevfilter->dev->lock); | |
| return 0; | |
| } | |
| del_timer(&dmxdevfilter->timer); | |
| dprintk("section callback %*ph\n", 6, buffer1); | |
| if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) { | |
| ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx, | |
| buffer1, buffer1_len, | |
| buffer_flags); | |
| if (ret == buffer1_len) | |
| ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx, | |
| buffer2, buffer2_len, | |
| buffer_flags); | |
| } else { | |
| ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, | |
| buffer1, buffer1_len); | |
| if (ret == buffer1_len) { | |
| ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, | |
| buffer2, buffer2_len); | |
| } | |
| } | |
| if (ret < 0) | |
| dmxdevfilter->buffer.error = ret; | |
| if (dmxdevfilter->params.sec.flags & DMX_ONESHOT) | |
| dmxdevfilter->state = DMXDEV_STATE_DONE; | |
| spin_unlock(&dmxdevfilter->dev->lock); | |
| wake_up(&dmxdevfilter->buffer.queue); | |
| return 0; | |
| } | |
| static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, | |
| const u8 *buffer2, size_t buffer2_len, | |
| struct dmx_ts_feed *feed, | |
| u32 *buffer_flags) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = feed->priv; | |
| struct dvb_ringbuffer *buffer; | |
| #ifdef CONFIG_DVB_MMAP | |
| struct dvb_vb2_ctx *ctx; | |
| #endif | |
| int ret; | |
| spin_lock(&dmxdevfilter->dev->lock); | |
| if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) { | |
| spin_unlock(&dmxdevfilter->dev->lock); | |
| return 0; | |
| } | |
| if (dmxdevfilter->params.pes.output == DMX_OUT_TAP || | |
| dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) { | |
| buffer = &dmxdevfilter->buffer; | |
| #ifdef CONFIG_DVB_MMAP | |
| ctx = &dmxdevfilter->vb2_ctx; | |
| #endif | |
| } else { | |
| buffer = &dmxdevfilter->dev->dvr_buffer; | |
| #ifdef CONFIG_DVB_MMAP | |
| ctx = &dmxdevfilter->dev->dvr_vb2_ctx; | |
| #endif | |
| } | |
| if (dvb_vb2_is_streaming(ctx)) { | |
| ret = dvb_vb2_fill_buffer(ctx, buffer1, buffer1_len, | |
| buffer_flags); | |
| if (ret == buffer1_len) | |
| ret = dvb_vb2_fill_buffer(ctx, buffer2, buffer2_len, | |
| buffer_flags); | |
| } else { | |
| if (buffer->error) { | |
| spin_unlock(&dmxdevfilter->dev->lock); | |
| wake_up(&buffer->queue); | |
| return 0; | |
| } | |
| ret = dvb_dmxdev_buffer_write(buffer, buffer1, buffer1_len); | |
| if (ret == buffer1_len) | |
| ret = dvb_dmxdev_buffer_write(buffer, | |
| buffer2, buffer2_len); | |
| } | |
| if (ret < 0) | |
| buffer->error = ret; | |
| spin_unlock(&dmxdevfilter->dev->lock); | |
| wake_up(&buffer->queue); | |
| return 0; | |
| } | |
| /* stop feed but only mark the specified filter as stopped (state set) */ | |
| static int dvb_dmxdev_feed_stop(struct dmxdev_filter *dmxdevfilter) | |
| { | |
| struct dmxdev_feed *feed; | |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); | |
| switch (dmxdevfilter->type) { | |
| case DMXDEV_TYPE_SEC: | |
| del_timer(&dmxdevfilter->timer); | |
| dmxdevfilter->feed.sec->stop_filtering(dmxdevfilter->feed.sec); | |
| break; | |
| case DMXDEV_TYPE_PES: | |
| list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) | |
| feed->ts->stop_filtering(feed->ts); | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| /* start feed associated with the specified filter */ | |
| static int dvb_dmxdev_feed_start(struct dmxdev_filter *filter) | |
| { | |
| struct dmxdev_feed *feed; | |
| int ret; | |
| dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); | |
| switch (filter->type) { | |
| case DMXDEV_TYPE_SEC: | |
| return filter->feed.sec->start_filtering(filter->feed.sec); | |
| case DMXDEV_TYPE_PES: | |
| list_for_each_entry(feed, &filter->feed.ts, next) { | |
| ret = feed->ts->start_filtering(feed->ts); | |
| if (ret < 0) { | |
| dvb_dmxdev_feed_stop(filter); | |
| return ret; | |
| } | |
| } | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| /* restart section feed if it has filters left associated with it, | |
| otherwise release the feed */ | |
| static int dvb_dmxdev_feed_restart(struct dmxdev_filter *filter) | |
| { | |
| int i; | |
| struct dmxdev *dmxdev = filter->dev; | |
| u16 pid = filter->params.sec.pid; | |
| for (i = 0; i < dmxdev->filternum; i++) | |
| if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && | |
| dmxdev->filter[i].type == DMXDEV_TYPE_SEC && | |
| dmxdev->filter[i].params.sec.pid == pid) { | |
| dvb_dmxdev_feed_start(&dmxdev->filter[i]); | |
| return 0; | |
| } | |
| filter->dev->demux->release_section_feed(dmxdev->demux, | |
| filter->feed.sec); | |
| return 0; | |
| } | |
| static int dvb_dmxdev_filter_stop(struct dmxdev_filter *dmxdevfilter) | |
| { | |
| struct dmxdev_feed *feed; | |
| struct dmx_demux *demux; | |
| if (dmxdevfilter->state < DMXDEV_STATE_GO) | |
| return 0; | |
| switch (dmxdevfilter->type) { | |
| case DMXDEV_TYPE_SEC: | |
| if (!dmxdevfilter->feed.sec) | |
| break; | |
| dvb_dmxdev_feed_stop(dmxdevfilter); | |
| if (dmxdevfilter->filter.sec) | |
| dmxdevfilter->feed.sec-> | |
| release_filter(dmxdevfilter->feed.sec, | |
| dmxdevfilter->filter.sec); | |
| dvb_dmxdev_feed_restart(dmxdevfilter); | |
| dmxdevfilter->feed.sec = NULL; | |
| break; | |
| case DMXDEV_TYPE_PES: | |
| dvb_dmxdev_feed_stop(dmxdevfilter); | |
| demux = dmxdevfilter->dev->demux; | |
| list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) { | |
| demux->release_ts_feed(demux, feed->ts); | |
| feed->ts = NULL; | |
| } | |
| break; | |
| default: | |
| if (dmxdevfilter->state == DMXDEV_STATE_ALLOCATED) | |
| return 0; | |
| return -EINVAL; | |
| } | |
| dvb_ringbuffer_flush(&dmxdevfilter->buffer); | |
| return 0; | |
| } | |
| static void dvb_dmxdev_delete_pids(struct dmxdev_filter *dmxdevfilter) | |
| { | |
| struct dmxdev_feed *feed, *tmp; | |
| /* delete all PIDs */ | |
| list_for_each_entry_safe(feed, tmp, &dmxdevfilter->feed.ts, next) { | |
| list_del(&feed->next); | |
| kfree(feed); | |
| } | |
| BUG_ON(!list_empty(&dmxdevfilter->feed.ts)); | |
| } | |
| static inline int dvb_dmxdev_filter_reset(struct dmxdev_filter *dmxdevfilter) | |
| { | |
| if (dmxdevfilter->state < DMXDEV_STATE_SET) | |
| return 0; | |
| if (dmxdevfilter->type == DMXDEV_TYPE_PES) | |
| dvb_dmxdev_delete_pids(dmxdevfilter); | |
| dmxdevfilter->type = DMXDEV_TYPE_NONE; | |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); | |
| return 0; | |
| } | |
| static int dvb_dmxdev_start_feed(struct dmxdev *dmxdev, | |
| struct dmxdev_filter *filter, | |
| struct dmxdev_feed *feed) | |
| { | |
| ktime_t timeout = ktime_set(0, 0); | |
| struct dmx_pes_filter_params *para = &filter->params.pes; | |
| enum dmx_output otype; | |
| int ret; | |
| int ts_type; | |
| enum dmx_ts_pes ts_pes; | |
| struct dmx_ts_feed *tsfeed; | |
| feed->ts = NULL; | |
| otype = para->output; | |
| ts_pes = para->pes_type; | |
| if (ts_pes < DMX_PES_OTHER) | |
| ts_type = TS_DECODER; | |
| else | |
| ts_type = 0; | |
| if (otype == DMX_OUT_TS_TAP) | |
| ts_type |= TS_PACKET; | |
| else if (otype == DMX_OUT_TSDEMUX_TAP) | |
| ts_type |= TS_PACKET | TS_DEMUX; | |
| else if (otype == DMX_OUT_TAP) | |
| ts_type |= TS_PACKET | TS_DEMUX | TS_PAYLOAD_ONLY; | |
| ret = dmxdev->demux->allocate_ts_feed(dmxdev->demux, &feed->ts, | |
| dvb_dmxdev_ts_callback); | |
| if (ret < 0) | |
| return ret; | |
| tsfeed = feed->ts; | |
| tsfeed->priv = filter; | |
| ret = tsfeed->set(tsfeed, feed->pid, ts_type, ts_pes, timeout); | |
| if (ret < 0) { | |
| dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); | |
| return ret; | |
| } | |
| ret = tsfeed->start_filtering(tsfeed); | |
| if (ret < 0) { | |
| dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) | |
| { | |
| struct dmxdev *dmxdev = filter->dev; | |
| struct dmxdev_feed *feed; | |
| void *mem; | |
| int ret, i; | |
| if (filter->state < DMXDEV_STATE_SET) | |
| return -EINVAL; | |
| if (filter->state >= DMXDEV_STATE_GO) | |
| dvb_dmxdev_filter_stop(filter); | |
| if (!filter->buffer.data) { | |
| mem = vmalloc(filter->buffer.size); | |
| if (!mem) | |
| return -ENOMEM; | |
| spin_lock_irq(&filter->dev->lock); | |
| filter->buffer.data = mem; | |
| spin_unlock_irq(&filter->dev->lock); | |
| } | |
| dvb_ringbuffer_flush(&filter->buffer); | |
| switch (filter->type) { | |
| case DMXDEV_TYPE_SEC: | |
| { | |
| struct dmx_sct_filter_params *para = &filter->params.sec; | |
| struct dmx_section_filter **secfilter = &filter->filter.sec; | |
| struct dmx_section_feed **secfeed = &filter->feed.sec; | |
| *secfilter = NULL; | |
| *secfeed = NULL; | |
| /* find active filter/feed with same PID */ | |
| for (i = 0; i < dmxdev->filternum; i++) { | |
| if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && | |
| dmxdev->filter[i].type == DMXDEV_TYPE_SEC && | |
| dmxdev->filter[i].params.sec.pid == para->pid) { | |
| *secfeed = dmxdev->filter[i].feed.sec; | |
| break; | |
| } | |
| } | |
| /* if no feed found, try to allocate new one */ | |
| if (!*secfeed) { | |
| ret = dmxdev->demux->allocate_section_feed(dmxdev->demux, | |
| secfeed, | |
| dvb_dmxdev_section_callback); | |
| if (ret < 0) { | |
| pr_err("DVB (%s): could not alloc feed\n", | |
| __func__); | |
| return ret; | |
| } | |
| ret = (*secfeed)->set(*secfeed, para->pid, | |
| (para->flags & DMX_CHECK_CRC) ? 1 : 0); | |
| if (ret < 0) { | |
| pr_err("DVB (%s): could not set feed\n", | |
| __func__); | |
| dvb_dmxdev_feed_restart(filter); | |
| return ret; | |
| } | |
| } else { | |
| dvb_dmxdev_feed_stop(filter); | |
| } | |
| ret = (*secfeed)->allocate_filter(*secfeed, secfilter); | |
| if (ret < 0) { | |
| dvb_dmxdev_feed_restart(filter); | |
| filter->feed.sec->start_filtering(*secfeed); | |
| dprintk("could not get filter\n"); | |
| return ret; | |
| } | |
| (*secfilter)->priv = filter; | |
| memcpy(&((*secfilter)->filter_value[3]), | |
| &(para->filter.filter[1]), DMX_FILTER_SIZE - 1); | |
| memcpy(&(*secfilter)->filter_mask[3], | |
| ¶->filter.mask[1], DMX_FILTER_SIZE - 1); | |
| memcpy(&(*secfilter)->filter_mode[3], | |
| ¶->filter.mode[1], DMX_FILTER_SIZE - 1); | |
| (*secfilter)->filter_value[0] = para->filter.filter[0]; | |
| (*secfilter)->filter_mask[0] = para->filter.mask[0]; | |
| (*secfilter)->filter_mode[0] = para->filter.mode[0]; | |
| (*secfilter)->filter_mask[1] = 0; | |
| (*secfilter)->filter_mask[2] = 0; | |
| filter->todo = 0; | |
| ret = filter->feed.sec->start_filtering(filter->feed.sec); | |
| if (ret < 0) | |
| return ret; | |
| dvb_dmxdev_filter_timer(filter); | |
| break; | |
| } | |
| case DMXDEV_TYPE_PES: | |
| list_for_each_entry(feed, &filter->feed.ts, next) { | |
| ret = dvb_dmxdev_start_feed(dmxdev, filter, feed); | |
| if (ret < 0) { | |
| dvb_dmxdev_filter_stop(filter); | |
| return ret; | |
| } | |
| } | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); | |
| return 0; | |
| } | |
| static int dvb_demux_open(struct inode *inode, struct file *file) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| int i; | |
| struct dmxdev_filter *dmxdevfilter; | |
| if (!dmxdev->filter) | |
| return -EINVAL; | |
| if (mutex_lock_interruptible(&dmxdev->mutex)) | |
| return -ERESTARTSYS; | |
| for (i = 0; i < dmxdev->filternum; i++) | |
| if (dmxdev->filter[i].state == DMXDEV_STATE_FREE) | |
| break; | |
| if (i == dmxdev->filternum) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -EMFILE; | |
| } | |
| dmxdevfilter = &dmxdev->filter[i]; | |
| mutex_init(&dmxdevfilter->mutex); | |
| file->private_data = dmxdevfilter; | |
| #ifdef CONFIG_DVB_MMAP | |
| dmxdev->may_do_mmap = 1; | |
| #else | |
| dmxdev->may_do_mmap = 0; | |
| #endif | |
| dvb_ringbuffer_init(&dmxdevfilter->buffer, NULL, 8192); | |
| dvb_vb2_init(&dmxdevfilter->vb2_ctx, "demux_filter", | |
| file->f_flags & O_NONBLOCK); | |
| dmxdevfilter->type = DMXDEV_TYPE_NONE; | |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); | |
| timer_setup(&dmxdevfilter->timer, dvb_dmxdev_filter_timeout, 0); | |
| dvbdev->users++; | |
| mutex_unlock(&dmxdev->mutex); | |
| return 0; | |
| } | |
| static int dvb_dmxdev_filter_free(struct dmxdev *dmxdev, | |
| struct dmxdev_filter *dmxdevfilter) | |
| { | |
| mutex_lock(&dmxdev->mutex); | |
| mutex_lock(&dmxdevfilter->mutex); | |
| if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) | |
| dvb_vb2_stream_off(&dmxdevfilter->vb2_ctx); | |
| dvb_vb2_release(&dmxdevfilter->vb2_ctx); | |
| dvb_dmxdev_filter_stop(dmxdevfilter); | |
| dvb_dmxdev_filter_reset(dmxdevfilter); | |
| if (dmxdevfilter->buffer.data) { | |
| void *mem = dmxdevfilter->buffer.data; | |
| spin_lock_irq(&dmxdev->lock); | |
| dmxdevfilter->buffer.data = NULL; | |
| spin_unlock_irq(&dmxdev->lock); | |
| vfree(mem); | |
| } | |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_FREE); | |
| wake_up(&dmxdevfilter->buffer.queue); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| mutex_unlock(&dmxdev->mutex); | |
| return 0; | |
| } | |
| static inline void invert_mode(struct dmx_filter *filter) | |
| { | |
| int i; | |
| for (i = 0; i < DMX_FILTER_SIZE; i++) | |
| filter->mode[i] ^= 0xff; | |
| } | |
| static int dvb_dmxdev_add_pid(struct dmxdev *dmxdev, | |
| struct dmxdev_filter *filter, u16 pid) | |
| { | |
| struct dmxdev_feed *feed; | |
| if ((filter->type != DMXDEV_TYPE_PES) || | |
| (filter->state < DMXDEV_STATE_SET)) | |
| return -EINVAL; | |
| /* only TS packet filters may have multiple PIDs */ | |
| if ((filter->params.pes.output != DMX_OUT_TSDEMUX_TAP) && | |
| (!list_empty(&filter->feed.ts))) | |
| return -EINVAL; | |
| feed = kzalloc(sizeof(struct dmxdev_feed), GFP_KERNEL); | |
| if (feed == NULL) | |
| return -ENOMEM; | |
| feed->pid = pid; | |
| list_add(&feed->next, &filter->feed.ts); | |
| if (filter->state >= DMXDEV_STATE_GO) | |
| return dvb_dmxdev_start_feed(dmxdev, filter, feed); | |
| return 0; | |
| } | |
| static int dvb_dmxdev_remove_pid(struct dmxdev *dmxdev, | |
| struct dmxdev_filter *filter, u16 pid) | |
| { | |
| struct dmxdev_feed *feed, *tmp; | |
| if ((filter->type != DMXDEV_TYPE_PES) || | |
| (filter->state < DMXDEV_STATE_SET)) | |
| return -EINVAL; | |
| list_for_each_entry_safe(feed, tmp, &filter->feed.ts, next) { | |
| if ((feed->pid == pid) && (feed->ts != NULL)) { | |
| feed->ts->stop_filtering(feed->ts); | |
| filter->dev->demux->release_ts_feed(filter->dev->demux, | |
| feed->ts); | |
| list_del(&feed->next); | |
| kfree(feed); | |
| } | |
| } | |
| return 0; | |
| } | |
| static int dvb_dmxdev_filter_set(struct dmxdev *dmxdev, | |
| struct dmxdev_filter *dmxdevfilter, | |
| struct dmx_sct_filter_params *params) | |
| { | |
| dprintk("%s: PID=0x%04x, flags=%02x, timeout=%d\n", | |
| __func__, params->pid, params->flags, params->timeout); | |
| dvb_dmxdev_filter_stop(dmxdevfilter); | |
| dmxdevfilter->type = DMXDEV_TYPE_SEC; | |
| memcpy(&dmxdevfilter->params.sec, | |
| params, sizeof(struct dmx_sct_filter_params)); | |
| invert_mode(&dmxdevfilter->params.sec.filter); | |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); | |
| if (params->flags & DMX_IMMEDIATE_START) | |
| return dvb_dmxdev_filter_start(dmxdevfilter); | |
| return 0; | |
| } | |
| static int dvb_dmxdev_pes_filter_set(struct dmxdev *dmxdev, | |
| struct dmxdev_filter *dmxdevfilter, | |
| struct dmx_pes_filter_params *params) | |
| { | |
| int ret; | |
| dvb_dmxdev_filter_stop(dmxdevfilter); | |
| dvb_dmxdev_filter_reset(dmxdevfilter); | |
| if ((unsigned int)params->pes_type > DMX_PES_OTHER) | |
| return -EINVAL; | |
| dmxdevfilter->type = DMXDEV_TYPE_PES; | |
| memcpy(&dmxdevfilter->params, params, | |
| sizeof(struct dmx_pes_filter_params)); | |
| INIT_LIST_HEAD(&dmxdevfilter->feed.ts); | |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); | |
| ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, | |
| dmxdevfilter->params.pes.pid); | |
| if (ret < 0) | |
| return ret; | |
| if (params->flags & DMX_IMMEDIATE_START) | |
| return dvb_dmxdev_filter_start(dmxdevfilter); | |
| return 0; | |
| } | |
| static ssize_t dvb_dmxdev_read_sec(struct dmxdev_filter *dfil, | |
| struct file *file, char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| int result, hcount; | |
| int done = 0; | |
| if (dfil->todo <= 0) { | |
| hcount = 3 + dfil->todo; | |
| if (hcount > count) | |
| hcount = count; | |
| result = dvb_dmxdev_buffer_read(&dfil->buffer, | |
| file->f_flags & O_NONBLOCK, | |
| buf, hcount, ppos); | |
| if (result < 0) { | |
| dfil->todo = 0; | |
| return result; | |
| } | |
| if (copy_from_user(dfil->secheader - dfil->todo, buf, result)) | |
| return -EFAULT; | |
| buf += result; | |
| done = result; | |
| count -= result; | |
| dfil->todo -= result; | |
| if (dfil->todo > -3) | |
| return done; | |
| dfil->todo = ((dfil->secheader[1] << 8) | dfil->secheader[2]) & 0xfff; | |
| if (!count) | |
| return done; | |
| } | |
| if (count > dfil->todo) | |
| count = dfil->todo; | |
| result = dvb_dmxdev_buffer_read(&dfil->buffer, | |
| file->f_flags & O_NONBLOCK, | |
| buf, count, ppos); | |
| if (result < 0) | |
| return result; | |
| dfil->todo -= result; | |
| return (result + done); | |
| } | |
| static ssize_t | |
| dvb_demux_read(struct file *file, char __user *buf, size_t count, | |
| loff_t *ppos) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = file->private_data; | |
| int ret; | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) | |
| return -ERESTARTSYS; | |
| if (dmxdevfilter->type == DMXDEV_TYPE_SEC) | |
| ret = dvb_dmxdev_read_sec(dmxdevfilter, file, buf, count, ppos); | |
| else | |
| ret = dvb_dmxdev_buffer_read(&dmxdevfilter->buffer, | |
| file->f_flags & O_NONBLOCK, | |
| buf, count, ppos); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| return ret; | |
| } | |
| static int dvb_demux_do_ioctl(struct file *file, | |
| unsigned int cmd, void *parg) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = file->private_data; | |
| struct dmxdev *dmxdev = dmxdevfilter->dev; | |
| unsigned long arg = (unsigned long)parg; | |
| int ret = 0; | |
| if (mutex_lock_interruptible(&dmxdev->mutex)) | |
| return -ERESTARTSYS; | |
| switch (cmd) { | |
| case DMX_START: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| if (dmxdevfilter->state < DMXDEV_STATE_SET) | |
| ret = -EINVAL; | |
| else | |
| ret = dvb_dmxdev_filter_start(dmxdevfilter); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_STOP: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_dmxdev_filter_stop(dmxdevfilter); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_SET_FILTER: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_dmxdev_filter_set(dmxdev, dmxdevfilter, parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_SET_PES_FILTER: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_dmxdev_pes_filter_set(dmxdev, dmxdevfilter, parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_SET_BUFFER_SIZE: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_dmxdev_set_buffer_size(dmxdevfilter, arg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_GET_PES_PIDS: | |
| if (!dmxdev->demux->get_pes_pids) { | |
| ret = -EINVAL; | |
| break; | |
| } | |
| dmxdev->demux->get_pes_pids(dmxdev->demux, parg); | |
| break; | |
| case DMX_GET_STC: | |
| if (!dmxdev->demux->get_stc) { | |
| ret = -EINVAL; | |
| break; | |
| } | |
| ret = dmxdev->demux->get_stc(dmxdev->demux, | |
| ((struct dmx_stc *)parg)->num, | |
| &((struct dmx_stc *)parg)->stc, | |
| &((struct dmx_stc *)parg)->base); | |
| break; | |
| case DMX_ADD_PID: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| ret = -ERESTARTSYS; | |
| break; | |
| } | |
| ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, *(u16 *)parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_REMOVE_PID: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| ret = -ERESTARTSYS; | |
| break; | |
| } | |
| ret = dvb_dmxdev_remove_pid(dmxdev, dmxdevfilter, *(u16 *)parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| #ifdef CONFIG_DVB_MMAP | |
| case DMX_REQBUFS: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_vb2_reqbufs(&dmxdevfilter->vb2_ctx, parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_QUERYBUF: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_vb2_querybuf(&dmxdevfilter->vb2_ctx, parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_EXPBUF: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_vb2_expbuf(&dmxdevfilter->vb2_ctx, parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_QBUF: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_vb2_qbuf(&dmxdevfilter->vb2_ctx, parg); | |
| if (ret == 0 && !dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) | |
| ret = dvb_vb2_stream_on(&dmxdevfilter->vb2_ctx); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| case DMX_DQBUF: | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_vb2_dqbuf(&dmxdevfilter->vb2_ctx, parg); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| break; | |
| #endif | |
| default: | |
| ret = -ENOTTY; | |
| break; | |
| } | |
| mutex_unlock(&dmxdev->mutex); | |
| return ret; | |
| } | |
| static long dvb_demux_ioctl(struct file *file, unsigned int cmd, | |
| unsigned long arg) | |
| { | |
| return dvb_usercopy(file, cmd, arg, dvb_demux_do_ioctl); | |
| } | |
| static __poll_t dvb_demux_poll(struct file *file, poll_table *wait) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = file->private_data; | |
| __poll_t mask = 0; | |
| poll_wait(file, &dmxdevfilter->buffer.queue, wait); | |
| if ((!dmxdevfilter) || dmxdevfilter->dev->exit) | |
| return EPOLLERR; | |
| if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) | |
| return dvb_vb2_poll(&dmxdevfilter->vb2_ctx, file, wait); | |
| if (dmxdevfilter->state != DMXDEV_STATE_GO && | |
| dmxdevfilter->state != DMXDEV_STATE_DONE && | |
| dmxdevfilter->state != DMXDEV_STATE_TIMEDOUT) | |
| return 0; | |
| if (dmxdevfilter->buffer.error) | |
| mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI | EPOLLERR); | |
| if (!dvb_ringbuffer_empty(&dmxdevfilter->buffer)) | |
| mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI); | |
| return mask; | |
| } | |
| #ifdef CONFIG_DVB_MMAP | |
| static int dvb_demux_mmap(struct file *file, struct vm_area_struct *vma) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = file->private_data; | |
| struct dmxdev *dmxdev = dmxdevfilter->dev; | |
| int ret; | |
| if (!dmxdev->may_do_mmap) | |
| return -ENOTTY; | |
| if (mutex_lock_interruptible(&dmxdev->mutex)) | |
| return -ERESTARTSYS; | |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { | |
| mutex_unlock(&dmxdev->mutex); | |
| return -ERESTARTSYS; | |
| } | |
| ret = dvb_vb2_mmap(&dmxdevfilter->vb2_ctx, vma); | |
| mutex_unlock(&dmxdevfilter->mutex); | |
| mutex_unlock(&dmxdev->mutex); | |
| return ret; | |
| } | |
| #endif | |
| static int dvb_demux_release(struct inode *inode, struct file *file) | |
| { | |
| struct dmxdev_filter *dmxdevfilter = file->private_data; | |
| struct dmxdev *dmxdev = dmxdevfilter->dev; | |
| int ret; | |
| ret = dvb_dmxdev_filter_free(dmxdev, dmxdevfilter); | |
| mutex_lock(&dmxdev->mutex); | |
| dmxdev->dvbdev->users--; | |
| if (dmxdev->dvbdev->users == 1 && dmxdev->exit == 1) { | |
| mutex_unlock(&dmxdev->mutex); | |
| wake_up(&dmxdev->dvbdev->wait_queue); | |
| } else | |
| mutex_unlock(&dmxdev->mutex); | |
| return ret; | |
| } | |
| static const struct file_operations dvb_demux_fops = { | |
| .owner = THIS_MODULE, | |
| .read = dvb_demux_read, | |
| .unlocked_ioctl = dvb_demux_ioctl, | |
| .compat_ioctl = dvb_demux_ioctl, | |
| .open = dvb_demux_open, | |
| .release = dvb_demux_release, | |
| .poll = dvb_demux_poll, | |
| .llseek = default_llseek, | |
| #ifdef CONFIG_DVB_MMAP | |
| .mmap = dvb_demux_mmap, | |
| #endif | |
| }; | |
| static const struct dvb_device dvbdev_demux = { | |
| .priv = NULL, | |
| .users = 1, | |
| .writers = 1, | |
| #if defined(CONFIG_MEDIA_CONTROLLER_DVB) | |
| .name = "dvb-demux", | |
| #endif | |
| .fops = &dvb_demux_fops | |
| }; | |
| static int dvb_dvr_do_ioctl(struct file *file, | |
| unsigned int cmd, void *parg) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| unsigned long arg = (unsigned long)parg; | |
| int ret; | |
| if (mutex_lock_interruptible(&dmxdev->mutex)) | |
| return -ERESTARTSYS; | |
| switch (cmd) { | |
| case DMX_SET_BUFFER_SIZE: | |
| ret = dvb_dvr_set_buffer_size(dmxdev, arg); | |
| break; | |
| #ifdef CONFIG_DVB_MMAP | |
| case DMX_REQBUFS: | |
| ret = dvb_vb2_reqbufs(&dmxdev->dvr_vb2_ctx, parg); | |
| break; | |
| case DMX_QUERYBUF: | |
| ret = dvb_vb2_querybuf(&dmxdev->dvr_vb2_ctx, parg); | |
| break; | |
| case DMX_EXPBUF: | |
| ret = dvb_vb2_expbuf(&dmxdev->dvr_vb2_ctx, parg); | |
| break; | |
| case DMX_QBUF: | |
| ret = dvb_vb2_qbuf(&dmxdev->dvr_vb2_ctx, parg); | |
| if (ret == 0 && !dvb_vb2_is_streaming(&dmxdev->dvr_vb2_ctx)) | |
| ret = dvb_vb2_stream_on(&dmxdev->dvr_vb2_ctx); | |
| break; | |
| case DMX_DQBUF: | |
| ret = dvb_vb2_dqbuf(&dmxdev->dvr_vb2_ctx, parg); | |
| break; | |
| #endif | |
| default: | |
| ret = -ENOTTY; | |
| break; | |
| } | |
| mutex_unlock(&dmxdev->mutex); | |
| return ret; | |
| } | |
| static long dvb_dvr_ioctl(struct file *file, | |
| unsigned int cmd, unsigned long arg) | |
| { | |
| return dvb_usercopy(file, cmd, arg, dvb_dvr_do_ioctl); | |
| } | |
| static __poll_t dvb_dvr_poll(struct file *file, poll_table *wait) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| __poll_t mask = 0; | |
| dprintk("%s\n", __func__); | |
| poll_wait(file, &dmxdev->dvr_buffer.queue, wait); | |
| if (dmxdev->exit) | |
| return EPOLLERR; | |
| if (dvb_vb2_is_streaming(&dmxdev->dvr_vb2_ctx)) | |
| return dvb_vb2_poll(&dmxdev->dvr_vb2_ctx, file, wait); | |
| if (((file->f_flags & O_ACCMODE) == O_RDONLY) || | |
| dmxdev->may_do_mmap) { | |
| if (dmxdev->dvr_buffer.error) | |
| mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI | EPOLLERR); | |
| if (!dvb_ringbuffer_empty(&dmxdev->dvr_buffer)) | |
| mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI); | |
| } else | |
| mask |= (EPOLLOUT | EPOLLWRNORM | EPOLLPRI); | |
| return mask; | |
| } | |
| #ifdef CONFIG_DVB_MMAP | |
| static int dvb_dvr_mmap(struct file *file, struct vm_area_struct *vma) | |
| { | |
| struct dvb_device *dvbdev = file->private_data; | |
| struct dmxdev *dmxdev = dvbdev->priv; | |
| int ret; | |
| if (!dmxdev->may_do_mmap) | |
| return -ENOTTY; | |
| if (dmxdev->exit) | |
| return -ENODEV; | |
| if (mutex_lock_interruptible(&dmxdev->mutex)) | |
| return -ERESTARTSYS; | |
| ret = dvb_vb2_mmap(&dmxdev->dvr_vb2_ctx, vma); | |
| mutex_unlock(&dmxdev->mutex); | |
| return ret; | |
| } | |
| #endif | |
| static const struct file_operations dvb_dvr_fops = { | |
| .owner = THIS_MODULE, | |
| .read = dvb_dvr_read, | |
| .write = dvb_dvr_write, | |
| .unlocked_ioctl = dvb_dvr_ioctl, | |
| .open = dvb_dvr_open, | |
| .release = dvb_dvr_release, | |
| .poll = dvb_dvr_poll, | |
| .llseek = default_llseek, | |
| #ifdef CONFIG_DVB_MMAP | |
| .mmap = dvb_dvr_mmap, | |
| #endif | |
| }; | |
| static const struct dvb_device dvbdev_dvr = { | |
| .priv = NULL, | |
| .readers = 1, | |
| .users = 1, | |
| #if defined(CONFIG_MEDIA_CONTROLLER_DVB) | |
| .name = "dvb-dvr", | |
| #endif | |
| .fops = &dvb_dvr_fops | |
| }; | |
| int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) | |
| { | |
| int i; | |
| if (dmxdev->demux->open(dmxdev->demux) < 0) | |
| return -EUSERS; | |
| dmxdev->filter = vmalloc(array_size(sizeof(struct dmxdev_filter), | |
| dmxdev->filternum)); | |
| if (!dmxdev->filter) | |
| return -ENOMEM; | |
| mutex_init(&dmxdev->mutex); | |
| spin_lock_init(&dmxdev->lock); | |
| for (i = 0; i < dmxdev->filternum; i++) { | |
| dmxdev->filter[i].dev = dmxdev; | |
| dmxdev->filter[i].buffer.data = NULL; | |
| dvb_dmxdev_filter_state_set(&dmxdev->filter[i], | |
| DMXDEV_STATE_FREE); | |
| } | |
| dvb_register_device(dvb_adapter, &dmxdev->dvbdev, &dvbdev_demux, dmxdev, | |
| DVB_DEVICE_DEMUX, dmxdev->filternum); | |
| dvb_register_device(dvb_adapter, &dmxdev->dvr_dvbdev, &dvbdev_dvr, | |
| dmxdev, DVB_DEVICE_DVR, dmxdev->filternum); | |
| dvb_ringbuffer_init(&dmxdev->dvr_buffer, NULL, 8192); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(dvb_dmxdev_init); | |
| void dvb_dmxdev_release(struct dmxdev *dmxdev) | |
| { | |
| dmxdev->exit = 1; | |
| if (dmxdev->dvbdev->users > 1) { | |
| wait_event(dmxdev->dvbdev->wait_queue, | |
| dmxdev->dvbdev->users == 1); | |
| } | |
| if (dmxdev->dvr_dvbdev->users > 1) { | |
| wait_event(dmxdev->dvr_dvbdev->wait_queue, | |
| dmxdev->dvr_dvbdev->users == 1); | |
| } | |
| dvb_unregister_device(dmxdev->dvbdev); | |
| dvb_unregister_device(dmxdev->dvr_dvbdev); | |
| vfree(dmxdev->filter); | |
| dmxdev->filter = NULL; | |
| dmxdev->demux->close(dmxdev->demux); | |
| } | |
| EXPORT_SYMBOL(dvb_dmxdev_release); |