| /* |
| * 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. |
| * |
| * You should have received a copy of the GNU Lesser 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. |
| * |
| */ |
| |
| #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 <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/compat.h> |
| #include <linux/mm.h> |
| #include "dmxdev.h" |
| |
| static int overflow_auto_flush = 1; |
| module_param(overflow_auto_flush, int, 0644); |
| MODULE_PARM_DESC(overflow_auto_flush, |
| "Automatically flush buffer on overflow (default: on)"); |
| |
| #define DMX_DEFAULT_DECODER_BUFFER_SIZE (32768) |
| |
| static inline int dvb_dmxdev_verify_buffer_size(u32 size, u32 max_size, |
| u32 size_align) |
| { |
| if (size_align) |
| return size <= max_size && !(size % size_align); |
| else |
| return size <= max_size; |
| } |
| |
| static int dvb_filter_verify_buffer_size(struct dmxdev_filter *filter) |
| { |
| struct dmx_caps caps; |
| size_t size = filter->buffer.size; |
| |
| /* |
| * For backward compatibility, if no demux capabilities can |
| * be retrieved assume size is ok. |
| * Decoder filter buffer size is verified when decoder buffer is set. |
| */ |
| if (filter->dev->demux->get_caps) { |
| filter->dev->demux->get_caps(filter->dev->demux, &caps); |
| |
| if (filter->type == DMXDEV_TYPE_SEC) |
| return dvb_dmxdev_verify_buffer_size( |
| size, |
| caps.section.max_size, |
| caps.section.size_alignment); |
| |
| if (filter->params.pes.output == DMX_OUT_TAP) |
| return dvb_dmxdev_verify_buffer_size( |
| size, |
| caps.pes.max_size, |
| caps.pes.size_alignment); |
| |
| size = (filter->params.pes.output == DMX_OUT_TS_TAP) ? |
| filter->dev->dvr_buffer.size : size; |
| |
| if (filter->params.pes.output == DMX_OUT_TSDEMUX_TAP || |
| filter->params.pes.output == DMX_OUT_TS_TAP) { |
| if (filter->dmx_tsp_format == DMX_TSP_FORMAT_188) |
| return dvb_dmxdev_verify_buffer_size( |
| size, |
| caps.recording_188_tsp.max_size, |
| caps.recording_188_tsp.size_alignment); |
| |
| return dvb_dmxdev_verify_buffer_size( |
| size, |
| caps.recording_192_tsp.max_size, |
| caps.recording_192_tsp.size_alignment); |
| } |
| } |
| |
| return 1; |
| } |
| |
| 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) { |
| pr_debug("dmxdev: buffer overflow\n"); |
| return -EOVERFLOW; |
| } |
| |
| return dvb_ringbuffer_write(buf, src, len); |
| } |
| |
| static inline void dvb_dmxdev_notify_data_read(struct dmxdev_filter *filter, |
| int bytes_read) |
| { |
| if (!filter) |
| return; |
| |
| if (filter->type == DMXDEV_TYPE_SEC) { |
| if (filter->feed.sec.feed->notify_data_read) |
| filter->feed.sec.feed->notify_data_read( |
| filter->filter.sec, |
| bytes_read); |
| } else { |
| struct dmxdev_feed *feed; |
| |
| /* |
| * All feeds of same demux-handle share the same output |
| * buffer, it is enough to notify on the buffer status |
| * on one of the feeds |
| */ |
| feed = list_first_entry(&filter->feed.ts, |
| struct dmxdev_feed, next); |
| |
| if (feed->ts->notify_data_read) |
| feed->ts->notify_data_read( |
| feed->ts, |
| bytes_read); |
| } |
| } |
| |
| static inline u32 dvb_dmxdev_advance_event_idx(u32 index) |
| { |
| index++; |
| if (index >= DMX_EVENT_QUEUE_SIZE) |
| index = 0; |
| |
| return index; |
| } |
| |
| static inline int dvb_dmxdev_events_is_full(struct dmxdev_events_queue *events) |
| { |
| int new_write_index; |
| |
| new_write_index = dvb_dmxdev_advance_event_idx(events->write_index); |
| if (new_write_index == events->read_index) |
| return 1; |
| |
| return 0; |
| |
| } |
| |
| static inline void dvb_dmxdev_flush_events(struct dmxdev_events_queue *events) |
| { |
| events->read_index = 0; |
| events->write_index = 0; |
| events->notified_index = 0; |
| events->bytes_read_no_event = 0; |
| events->current_event_data_size = 0; |
| events->wakeup_events_counter = 0; |
| } |
| |
| static inline void dvb_dmxdev_flush_output(struct dvb_ringbuffer *buffer, |
| struct dmxdev_events_queue *events) |
| { |
| dvb_dmxdev_flush_events(events); |
| dvb_ringbuffer_flush(buffer); |
| } |
| |
| static int dvb_dmxdev_update_pes_event(struct dmx_filter_event *event, |
| int bytes_read) |
| { |
| int start_delta; |
| |
| if (event->params.pes.total_length <= bytes_read) |
| return event->params.pes.total_length; |
| |
| /* |
| * only part of the data relevant to this event was read. |
| * Update the event's information to reflect the new state. |
| */ |
| event->params.pes.total_length -= bytes_read; |
| |
| start_delta = event->params.pes.start_offset - |
| event->params.pes.base_offset; |
| |
| if (bytes_read <= start_delta) { |
| event->params.pes.base_offset += |
| bytes_read; |
| } else { |
| start_delta = |
| bytes_read - start_delta; |
| |
| event->params.pes.start_offset += start_delta; |
| event->params.pes.actual_length -= start_delta; |
| |
| event->params.pes.base_offset = |
| event->params.pes.start_offset; |
| } |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_update_section_event(struct dmx_filter_event *event, |
| int bytes_read) |
| { |
| int start_delta; |
| |
| if (event->params.section.total_length <= bytes_read) |
| return event->params.section.total_length; |
| |
| /* |
| * only part of the data relevant to this event was read. |
| * Update the event's information to reflect the new state. |
| */ |
| |
| event->params.section.total_length -= bytes_read; |
| |
| start_delta = event->params.section.start_offset - |
| event->params.section.base_offset; |
| |
| if (bytes_read <= start_delta) { |
| event->params.section.base_offset += |
| bytes_read; |
| } else { |
| start_delta = |
| bytes_read - start_delta; |
| |
| event->params.section.start_offset += start_delta; |
| event->params.section.actual_length -= start_delta; |
| |
| event->params.section.base_offset = |
| event->params.section.start_offset; |
| } |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_update_rec_event(struct dmx_filter_event *event, |
| int bytes_read) |
| { |
| if (event->params.recording_chunk.size <= bytes_read) |
| return event->params.recording_chunk.size; |
| |
| /* |
| * only part of the data relevant to this event was read. |
| * Update the event's information to reflect the new state. |
| */ |
| event->params.recording_chunk.size -= bytes_read; |
| event->params.recording_chunk.offset += bytes_read; |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_add_event(struct dmxdev_events_queue *events, |
| struct dmx_filter_event *event) |
| { |
| int res; |
| int new_write_index; |
| int data_event; |
| |
| /* Check if the event is disabled */ |
| if (events->event_mask.disable_mask & event->type) |
| return 0; |
| |
| /* Check if we are adding an event that user already read its data */ |
| if (events->bytes_read_no_event) { |
| data_event = 1; |
| |
| if (event->type == DMX_EVENT_NEW_PES) |
| res = dvb_dmxdev_update_pes_event(event, |
| events->bytes_read_no_event); |
| else if (event->type == DMX_EVENT_NEW_SECTION) |
| res = dvb_dmxdev_update_section_event(event, |
| events->bytes_read_no_event); |
| else if (event->type == DMX_EVENT_NEW_REC_CHUNK) |
| res = dvb_dmxdev_update_rec_event(event, |
| events->bytes_read_no_event); |
| else |
| data_event = 0; |
| |
| if (data_event) { |
| if (res) { |
| /* |
| * Data relevant to this event was fully |
| * consumed already, discard event. |
| */ |
| events->bytes_read_no_event -= res; |
| return 0; |
| } |
| events->bytes_read_no_event = 0; |
| } else { |
| /* |
| * data was read beyond the non-data event, |
| * making it not relevant anymore |
| */ |
| return 0; |
| } |
| } |
| |
| new_write_index = dvb_dmxdev_advance_event_idx(events->write_index); |
| if (new_write_index == events->read_index) { |
| pr_err("dmxdev: events overflow\n"); |
| return -EOVERFLOW; |
| } |
| |
| events->queue[events->write_index] = *event; |
| events->write_index = new_write_index; |
| |
| if (!(events->event_mask.no_wakeup_mask & event->type)) |
| events->wakeup_events_counter++; |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_remove_event(struct dmxdev_events_queue *events, |
| struct dmx_filter_event *event) |
| { |
| if (events->notified_index == events->write_index) |
| return -ENODATA; |
| |
| *event = events->queue[events->notified_index]; |
| |
| events->notified_index = |
| dvb_dmxdev_advance_event_idx(events->notified_index); |
| |
| if (!(events->event_mask.no_wakeup_mask & event->type)) |
| events->wakeup_events_counter--; |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_update_events(struct dmxdev_events_queue *events, |
| int bytes_read) |
| { |
| struct dmx_filter_event *event; |
| int res; |
| int data_event; |
| |
| /* |
| * If data events are not enabled on this filter, |
| * there's nothing to update. |
| */ |
| if (events->data_read_event_masked) |
| return 0; |
| |
| /* |
| * Go through all events that were notified and |
| * remove them from the events queue if their respective |
| * data was read. |
| */ |
| while ((events->read_index != events->notified_index) && |
| (bytes_read)) { |
| event = events->queue + events->read_index; |
| |
| data_event = 1; |
| |
| if (event->type == DMX_EVENT_NEW_PES) |
| res = dvb_dmxdev_update_pes_event(event, bytes_read); |
| else if (event->type == DMX_EVENT_NEW_SECTION) |
| res = dvb_dmxdev_update_section_event(event, |
| bytes_read); |
| else if (event->type == DMX_EVENT_NEW_REC_CHUNK) |
| res = dvb_dmxdev_update_rec_event(event, bytes_read); |
| else |
| data_event = 0; |
| |
| if (data_event) { |
| if (res) { |
| /* |
| * Data relevant to this event was |
| * fully consumed, remove it from the queue. |
| */ |
| bytes_read -= res; |
| events->read_index = |
| dvb_dmxdev_advance_event_idx( |
| events->read_index); |
| } else { |
| bytes_read = 0; |
| } |
| } else { |
| /* |
| * non-data event was already notified, |
| * no need to keep it |
| */ |
| events->read_index = dvb_dmxdev_advance_event_idx( |
| events->read_index); |
| } |
| } |
| |
| if (!bytes_read) |
| return 0; |
| |
| /* |
| * If we reached here it means: |
| * bytes_read != 0 |
| * events->read_index == events->notified_index |
| * Check if there are pending events in the queue |
| * which the user didn't read while their relevant data |
| * was read. |
| */ |
| while ((events->notified_index != events->write_index) && |
| (bytes_read)) { |
| event = events->queue + events->notified_index; |
| |
| data_event = 1; |
| |
| if (event->type == DMX_EVENT_NEW_PES) |
| res = dvb_dmxdev_update_pes_event(event, bytes_read); |
| else if (event->type == DMX_EVENT_NEW_SECTION) |
| res = dvb_dmxdev_update_section_event(event, |
| bytes_read); |
| else if (event->type == DMX_EVENT_NEW_REC_CHUNK) |
| res = dvb_dmxdev_update_rec_event(event, bytes_read); |
| else |
| data_event = 0; |
| |
| if (data_event) { |
| if (res) { |
| /* |
| * Data relevant to this event was |
| * fully consumed, remove it from the queue. |
| */ |
| bytes_read -= res; |
| events->notified_index = |
| dvb_dmxdev_advance_event_idx( |
| events->notified_index); |
| if (!(events->event_mask.no_wakeup_mask & |
| event->type)) |
| events->wakeup_events_counter--; |
| } else { |
| bytes_read = 0; |
| } |
| } else { |
| if (bytes_read) |
| /* |
| * data was read beyond the non-data event, |
| * making it not relevant anymore |
| */ |
| events->notified_index = |
| dvb_dmxdev_advance_event_idx( |
| events->notified_index); |
| if (!(events->event_mask.no_wakeup_mask & |
| event->type)) |
| events->wakeup_events_counter--; |
| } |
| |
| events->read_index = events->notified_index; |
| } |
| |
| /* |
| * Check if data was read without having a respective |
| * event in the events-queue |
| */ |
| if (bytes_read) |
| events->bytes_read_no_event += bytes_read; |
| |
| return 0; |
| } |
| |
| static inline int dvb_dmxdev_check_data(struct dmxdev_filter *filter, |
| struct dvb_ringbuffer *src) |
| { |
| int data_status_change; |
| |
| if (filter) |
| if (mutex_lock_interruptible(&filter->mutex)) |
| return -ERESTARTSYS; |
| |
| if (!src->data || |
| !dvb_ringbuffer_empty(src) || |
| src->error || |
| (filter && |
| (filter->state != DMXDEV_STATE_GO) && |
| (filter->state != DMXDEV_STATE_DONE))) |
| data_status_change = 1; |
| else |
| data_status_change = 0; |
| |
| if (filter) |
| mutex_unlock(&filter->mutex); |
| |
| return data_status_change; |
| } |
| |
| static ssize_t dvb_dmxdev_buffer_read(struct dmxdev_filter *filter, |
| 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; |
| src->error = 0; |
| return ret; |
| } |
| |
| for (todo = count; todo > 0; todo -= ret) { |
| if (non_blocking && dvb_ringbuffer_empty(src)) { |
| ret = -EWOULDBLOCK; |
| break; |
| } |
| |
| if (filter) { |
| if ((filter->state == DMXDEV_STATE_DONE) && |
| dvb_ringbuffer_empty(src)) |
| break; |
| |
| mutex_unlock(&filter->mutex); |
| } |
| |
| ret = wait_event_interruptible(src->queue, |
| dvb_dmxdev_check_data(filter, src)); |
| |
| if (filter) { |
| if (mutex_lock_interruptible(&filter->mutex)) |
| return -ERESTARTSYS; |
| |
| if ((filter->state != DMXDEV_STATE_GO) && |
| (filter->state != DMXDEV_STATE_DONE)) |
| return -ENODEV; |
| } |
| |
| if (ret < 0) |
| break; |
| |
| if (!src->data) |
| return 0; |
| |
| if (src->error) { |
| ret = src->error; |
| src->error = 0; |
| 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; |
| } |
| |
| if (count - todo) /* some data was read? */ |
| wake_up_all(&src->queue); |
| |
| 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 void dvb_dvr_oob_cmd(struct dmxdev *dmxdev, struct dmx_oob_command *cmd) |
| { |
| int i; |
| struct dmxdev_filter *filter; |
| struct dmxdev_feed *feed; |
| |
| for (i = 0; i < dmxdev->filternum; i++) { |
| filter = &dmxdev->filter[i]; |
| if (!filter || filter->state != DMXDEV_STATE_GO) |
| continue; |
| |
| switch (filter->type) { |
| case DMXDEV_TYPE_SEC: |
| filter->feed.sec.feed->oob_command( |
| filter->feed.sec.feed, cmd); |
| break; |
| case DMXDEV_TYPE_PES: |
| feed = list_first_entry(&filter->feed.ts, |
| struct dmxdev_feed, next); |
| feed->ts->oob_command(feed->ts, cmd); |
| break; |
| case DMXDEV_TYPE_NONE: |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| static int dvb_dvr_feed_cmd(struct dmxdev *dmxdev, struct dvr_command *dvr_cmd) |
| { |
| int ret = 0; |
| size_t todo; |
| int bytes_written = 0; |
| size_t split; |
| size_t tsp_size; |
| u8 *data_start; |
| struct dvb_ringbuffer *src = &dmxdev->dvr_input_buffer; |
| |
| todo = dvr_cmd->cmd.data_feed_count; |
| |
| if (dmxdev->demux->get_tsp_size) |
| tsp_size = dmxdev->demux->get_tsp_size(dmxdev->demux); |
| else |
| tsp_size = 188; |
| |
| while (todo >= tsp_size) { |
| /* wait for input */ |
| ret = wait_event_interruptible( |
| src->queue, |
| (dvb_ringbuffer_avail(src) >= tsp_size) || |
| dmxdev->dvr_in_exit || src->error); |
| |
| if (ret < 0) |
| break; |
| |
| spin_lock(&dmxdev->dvr_in_lock); |
| |
| if (dmxdev->exit || dmxdev->dvr_in_exit) { |
| spin_unlock(&dmxdev->dvr_in_lock); |
| ret = -ENODEV; |
| break; |
| } |
| |
| if (src->error) { |
| spin_unlock(&dmxdev->dvr_in_lock); |
| wake_up_all(&src->queue); |
| ret = -EINVAL; |
| break; |
| } |
| |
| dmxdev->dvr_processing_input = 1; |
| |
| split = (src->pread + todo > src->size) ? |
| src->size - src->pread : 0; |
| |
| /* |
| * In DVR PULL mode, write might block. |
| * Lock on DVR buffer is released before calling to |
| * write, if DVR was released meanwhile, dvr_in_exit is |
| * prompted. Lock is acquired when updating the read pointer |
| * again to preserve read/write pointers consistency. |
| * |
| * In protected input mode, DVR input buffer is not mapped |
| * to kernel memory. Underlying demux implementation |
| * should trigger HW to read from DVR input buffer |
| * based on current read offset. |
| */ |
| if (split > 0) { |
| data_start = (dmxdev->demux->dvr_input_protected) ? |
| NULL : (src->data + src->pread); |
| |
| spin_unlock(&dmxdev->dvr_in_lock); |
| ret = dmxdev->demux->write(dmxdev->demux, |
| data_start, |
| split); |
| |
| if (ret < 0) { |
| pr_err("dmxdev: dvr write error %d\n", ret); |
| continue; |
| } |
| |
| if (dmxdev->dvr_in_exit) { |
| ret = -ENODEV; |
| break; |
| } |
| |
| spin_lock(&dmxdev->dvr_in_lock); |
| |
| todo -= ret; |
| bytes_written += ret; |
| DVB_RINGBUFFER_SKIP(src, ret); |
| if (ret < split) { |
| dmxdev->dvr_processing_input = 0; |
| spin_unlock(&dmxdev->dvr_in_lock); |
| wake_up_all(&src->queue); |
| continue; |
| } |
| } |
| |
| data_start = (dmxdev->demux->dvr_input_protected) ? |
| NULL : (src->data + src->pread); |
| |
| spin_unlock(&dmxdev->dvr_in_lock); |
| ret = dmxdev->demux->write(dmxdev->demux, |
| data_start, todo); |
| |
| if (ret < 0) { |
| pr_err("dmxdev: dvr write error %d\n", ret); |
| continue; |
| } |
| |
| if (dmxdev->dvr_in_exit) { |
| ret = -ENODEV; |
| break; |
| } |
| |
| spin_lock(&dmxdev->dvr_in_lock); |
| |
| todo -= ret; |
| bytes_written += ret; |
| DVB_RINGBUFFER_SKIP(src, ret); |
| dmxdev->dvr_processing_input = 0; |
| spin_unlock(&dmxdev->dvr_in_lock); |
| |
| wake_up_all(&src->queue); |
| } |
| |
| if (ret < 0) |
| return ret; |
| |
| return bytes_written; |
| } |
| |
| static int dvr_input_thread_entry(void *arg) |
| { |
| struct dmxdev *dmxdev = arg; |
| struct dvb_ringbuffer *cmdbuf = &dmxdev->dvr_cmd_buffer; |
| struct dvr_command dvr_cmd; |
| int leftover = 0; |
| int ret; |
| |
| while (1) { |
| /* wait for input */ |
| ret = wait_event_interruptible( |
| cmdbuf->queue, |
| (!cmdbuf->data) || |
| (dvb_ringbuffer_avail(cmdbuf) >= sizeof(dvr_cmd)) || |
| (dmxdev->dvr_in_exit)); |
| |
| if (ret < 0) |
| break; |
| |
| spin_lock(&dmxdev->dvr_in_lock); |
| |
| if (!cmdbuf->data || dmxdev->exit || dmxdev->dvr_in_exit) { |
| spin_unlock(&dmxdev->dvr_in_lock); |
| break; |
| } |
| |
| dvb_ringbuffer_read(cmdbuf, (u8 *)&dvr_cmd, sizeof(dvr_cmd)); |
| |
| spin_unlock(&dmxdev->dvr_in_lock); |
| |
| if (dvr_cmd.type == DVR_DATA_FEED_CMD) { |
| dvr_cmd.cmd.data_feed_count += leftover; |
| |
| ret = dvb_dvr_feed_cmd(dmxdev, &dvr_cmd); |
| if (ret < 0) { |
| pr_debug("%s: DVR data feed failed, ret=%d\n", |
| __func__, ret); |
| continue; |
| } |
| |
| leftover = dvr_cmd.cmd.data_feed_count - ret; |
| } else { |
| /* |
| * For EOS, try to process leftover data in the input |
| * buffer. |
| */ |
| if (dvr_cmd.cmd.oobcmd.type == DMX_OOB_CMD_EOS) { |
| struct dvr_command feed_cmd; |
| |
| feed_cmd.type = DVR_DATA_FEED_CMD; |
| feed_cmd.cmd.data_feed_count = |
| dvb_ringbuffer_avail( |
| &dmxdev->dvr_input_buffer); |
| dvb_dvr_feed_cmd(dmxdev, &feed_cmd); |
| } |
| |
| dvb_dvr_oob_cmd(dmxdev, &dvr_cmd.cmd.oobcmd); |
| } |
| } |
| |
| set_current_state(TASK_INTERRUPTIBLE); |
| while (!kthread_should_stop()) { |
| schedule(); |
| set_current_state(TASK_INTERRUPTIBLE); |
| } |
| set_current_state(TASK_RUNNING); |
| |
| return 0; |
| } |
| |
| 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; |
| void *mem; |
| |
| pr_debug("function : %s(%X)\n", __func__, (file->f_flags & O_ACCMODE)); |
| |
| if (mutex_lock_interruptible(&dmxdev->mutex)) |
| return -ERESTARTSYS; |
| |
| if (dmxdev->exit) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ENODEV; |
| } |
| |
| if ((file->f_flags & O_ACCMODE) == O_RDWR) { |
| if (!(dmxdev->capabilities & DMXDEV_CAP_DUPLEX)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| if ((file->f_flags & O_ACCMODE) == O_RDONLY) { |
| if (!dvbdev->readers) { |
| mutex_unlock(&dmxdev->mutex); |
| return -EBUSY; |
| } |
| mem = vmalloc_user(DVR_BUFFER_SIZE); |
| if (!mem) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ENOMEM; |
| } |
| dvb_ringbuffer_init(&dmxdev->dvr_buffer, mem, DVR_BUFFER_SIZE); |
| dvb_dmxdev_flush_events(&dmxdev->dvr_output_events); |
| dmxdev->dvr_output_events.event_mask.disable_mask = 0; |
| dmxdev->dvr_output_events.event_mask.no_wakeup_mask = 0; |
| dmxdev->dvr_output_events.event_mask.wakeup_threshold = 1; |
| dmxdev->dvr_feeds_count = 0; |
| dmxdev->dvr_buffer_mode = DMX_BUFFER_MODE_INTERNAL; |
| dmxdev->dvr_priv_buff_handle = NULL; |
| |
| dvbdev->readers--; |
| } else if (!dvbdev->writers) { |
| dmxdev->dvr_in_exit = 0; |
| dmxdev->dvr_processing_input = 0; |
| 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; |
| } |
| |
| mem = vmalloc_user(DVR_BUFFER_SIZE); |
| if (!mem) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ENOMEM; |
| } |
| |
| dmxdev->demux->disconnect_frontend(dmxdev->demux); |
| dmxdev->demux->connect_frontend(dmxdev->demux, front); |
| dmxdev->dvr_input_buffer_mode = DMX_BUFFER_MODE_INTERNAL; |
| |
| dvb_ringbuffer_init(&dmxdev->dvr_input_buffer, |
| mem, |
| DVR_BUFFER_SIZE); |
| |
| dmxdev->demux->dvr_input.priv_handle = NULL; |
| dmxdev->demux->dvr_input.ringbuff = &dmxdev->dvr_input_buffer; |
| dmxdev->demux->dvr_input_protected = 0; |
| mem = vmalloc(DVR_CMDS_BUFFER_SIZE); |
| if (!mem) { |
| vfree(dmxdev->dvr_input_buffer.data); |
| dmxdev->dvr_input_buffer.data = NULL; |
| mutex_unlock(&dmxdev->mutex); |
| return -ENOMEM; |
| } |
| dvb_ringbuffer_init(&dmxdev->dvr_cmd_buffer, mem, |
| DVR_CMDS_BUFFER_SIZE); |
| dvbdev->writers--; |
| |
| dmxdev->dvr_input_thread = |
| kthread_run( |
| dvr_input_thread_entry, |
| (void *)dmxdev, |
| "dvr_input"); |
| |
| if (IS_ERR(dmxdev->dvr_input_thread)) { |
| vfree(dmxdev->dvr_input_buffer.data); |
| vfree(dmxdev->dvr_cmd_buffer.data); |
| dmxdev->dvr_input_buffer.data = NULL; |
| dmxdev->dvr_cmd_buffer.data = NULL; |
| mutex_unlock(&dmxdev->mutex); |
| return -ENOMEM; |
| } |
| } |
| |
| 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_RDONLY) { |
| dvbdev->readers++; |
| if (dmxdev->dvr_buffer.data) { |
| void *mem = dmxdev->dvr_buffer.data; |
| mb(); |
| spin_lock_irq(&dmxdev->lock); |
| dmxdev->dvr_buffer.data = NULL; |
| spin_unlock_irq(&dmxdev->lock); |
| wake_up_all(&dmxdev->dvr_buffer.queue); |
| |
| if (dmxdev->dvr_buffer_mode == DMX_BUFFER_MODE_INTERNAL) |
| vfree(mem); |
| } |
| |
| if ((dmxdev->dvr_buffer_mode == DMX_BUFFER_MODE_EXTERNAL) && |
| dmxdev->dvr_priv_buff_handle) { |
| dmxdev->demux->unmap_buffer(dmxdev->demux, |
| dmxdev->dvr_priv_buff_handle); |
| dmxdev->dvr_priv_buff_handle = NULL; |
| } |
| } else { |
| int i; |
| |
| spin_lock(&dmxdev->dvr_in_lock); |
| dmxdev->dvr_in_exit = 1; |
| spin_unlock(&dmxdev->dvr_in_lock); |
| |
| wake_up_all(&dmxdev->dvr_cmd_buffer.queue); |
| |
| /* |
| * There might be dmx filters reading now from DVR |
| * device, in PULL mode, they might be also stalled |
| * on output, signal to them that DVR is exiting. |
| */ |
| if (dmxdev->playback_mode == DMX_PB_MODE_PULL) { |
| wake_up_all(&dmxdev->dvr_buffer.queue); |
| |
| for (i = 0; i < dmxdev->filternum; i++) |
| if (dmxdev->filter[i].state == DMXDEV_STATE_GO) |
| wake_up_all( |
| &dmxdev->filter[i].buffer.queue); |
| } |
| |
| /* notify kernel demux that we are canceling */ |
| if (dmxdev->demux->write_cancel) |
| dmxdev->demux->write_cancel(dmxdev->demux); |
| |
| /* |
| * Now stop dvr-input thread so that no one |
| * would process data from dvr input buffer any more |
| * before it gets freed. |
| */ |
| kthread_stop(dmxdev->dvr_input_thread); |
| |
| dvbdev->writers++; |
| dmxdev->demux->disconnect_frontend(dmxdev->demux); |
| dmxdev->demux->connect_frontend(dmxdev->demux, |
| dmxdev->dvr_orig_fe); |
| |
| if (dmxdev->dvr_input_buffer.data) { |
| void *mem = dmxdev->dvr_input_buffer.data; |
| /* |
| * Ensure all the operations on the DVR input buffer |
| * are completed before it gets freed. |
| */ |
| mb(); |
| spin_lock_irq(&dmxdev->dvr_in_lock); |
| dmxdev->dvr_input_buffer.data = NULL; |
| spin_unlock_irq(&dmxdev->dvr_in_lock); |
| |
| if (dmxdev->dvr_input_buffer_mode == |
| DMX_BUFFER_MODE_INTERNAL) |
| vfree(mem); |
| } |
| |
| if ((dmxdev->dvr_input_buffer_mode == |
| DMX_BUFFER_MODE_EXTERNAL) && |
| (dmxdev->demux->dvr_input.priv_handle)) { |
| if (!dmxdev->demux->dvr_input_protected) |
| dmxdev->demux->unmap_buffer(dmxdev->demux, |
| dmxdev->demux->dvr_input.priv_handle); |
| dmxdev->demux->dvr_input.priv_handle = NULL; |
| } |
| |
| if (dmxdev->dvr_cmd_buffer.data) { |
| void *mem = dmxdev->dvr_cmd_buffer.data; |
| /* |
| * Ensure all the operations on the DVR command buffer |
| * are completed before it gets freed. |
| */ |
| mb(); |
| spin_lock_irq(&dmxdev->dvr_in_lock); |
| dmxdev->dvr_cmd_buffer.data = NULL; |
| spin_unlock_irq(&dmxdev->dvr_in_lock); |
| vfree(mem); |
| } |
| } |
| /* TODO */ |
| dvbdev->users--; |
| if (dvbdev->users == 1 && dmxdev->exit == 1) { |
| fops_put(file->f_op); |
| file->f_op = NULL; |
| mutex_unlock(&dmxdev->mutex); |
| wake_up(&dvbdev->wait_queue); |
| } else |
| mutex_unlock(&dmxdev->mutex); |
| |
| return 0; |
| } |
| |
| |
| static int dvb_dvr_mmap(struct file *filp, struct vm_area_struct *vma) |
| { |
| struct dvb_device *dvbdev = filp->private_data; |
| struct dmxdev *dmxdev = dvbdev->priv; |
| struct dvb_ringbuffer *buffer; |
| enum dmx_buffer_mode buffer_mode; |
| int vma_size; |
| int buffer_size; |
| int ret; |
| |
| if (((filp->f_flags & O_ACCMODE) == O_RDONLY) && |
| (vma->vm_flags & VM_WRITE)) |
| return -EINVAL; |
| |
| if (mutex_lock_interruptible(&dmxdev->mutex)) |
| return -ERESTARTSYS; |
| |
| if (dmxdev->exit) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ENODEV; |
| } |
| |
| if ((filp->f_flags & O_ACCMODE) == O_RDONLY) { |
| buffer = &dmxdev->dvr_buffer; |
| buffer_mode = dmxdev->dvr_buffer_mode; |
| } else { |
| buffer = &dmxdev->dvr_input_buffer; |
| buffer_mode = dmxdev->dvr_input_buffer_mode; |
| } |
| |
| if (buffer_mode == DMX_BUFFER_MODE_EXTERNAL) { |
| mutex_unlock(&dmxdev->mutex); |
| return -EINVAL; |
| } |
| |
| vma_size = vma->vm_end - vma->vm_start; |
| |
| /* Make sure requested mapping is not larger than buffer size */ |
| buffer_size = buffer->size + (PAGE_SIZE-1); |
| buffer_size = buffer_size & ~(PAGE_SIZE-1); |
| |
| if (vma_size != buffer_size) { |
| mutex_unlock(&dmxdev->mutex); |
| return -EINVAL; |
| } |
| |
| ret = remap_vmalloc_range(vma, buffer->data, 0); |
| if (ret) { |
| mutex_unlock(&dmxdev->mutex); |
| return ret; |
| } |
| |
| vma->vm_flags |= VM_DONTDUMP; |
| vma->vm_flags |= VM_DONTEXPAND; |
| |
| mutex_unlock(&dmxdev->mutex); |
| return ret; |
| } |
| |
| static void dvb_dvr_queue_data_feed(struct dmxdev *dmxdev, size_t count) |
| { |
| struct dvb_ringbuffer *cmdbuf = &dmxdev->dvr_cmd_buffer; |
| struct dvr_command *dvr_cmd; |
| int last_dvr_cmd; |
| |
| spin_lock(&dmxdev->dvr_in_lock); |
| |
| /* Peek at the last DVR command queued, try to coalesce FEED commands */ |
| if (dvb_ringbuffer_avail(cmdbuf) >= sizeof(*dvr_cmd)) { |
| last_dvr_cmd = cmdbuf->pwrite - sizeof(*dvr_cmd); |
| if (last_dvr_cmd < 0) |
| last_dvr_cmd += cmdbuf->size; |
| |
| dvr_cmd = (struct dvr_command *)&cmdbuf->data[last_dvr_cmd]; |
| if (dvr_cmd->type == DVR_DATA_FEED_CMD) { |
| dvr_cmd->cmd.data_feed_count += count; |
| spin_unlock(&dmxdev->dvr_in_lock); |
| return; |
| } |
| } |
| |
| /* |
| * We assume command buffer is large enough so that overflow should not |
| * happen. Overflow to the command buffer means data previously written |
| * to the input buffer is 'orphan' - does not have a matching FEED |
| * command. Issue a warning if this ever happens. |
| * Orphan data might still be processed if EOS is issued. |
| */ |
| if (dvb_ringbuffer_free(cmdbuf) < sizeof(*dvr_cmd)) { |
| pr_err("%s: DVR command buffer overflow\n", __func__); |
| spin_unlock(&dmxdev->dvr_in_lock); |
| return; |
| } |
| |
| dvr_cmd = (struct dvr_command *)&cmdbuf->data[cmdbuf->pwrite]; |
| dvr_cmd->type = DVR_DATA_FEED_CMD; |
| dvr_cmd->cmd.data_feed_count = count; |
| DVB_RINGBUFFER_PUSH(cmdbuf, sizeof(*dvr_cmd)); |
| spin_unlock(&dmxdev->dvr_in_lock); |
| |
| wake_up_all(&cmdbuf->queue); |
| } |
| |
| static int dvb_dvr_external_input_only(struct dmxdev *dmxdev) |
| { |
| struct dmx_caps caps; |
| int is_external_only; |
| int flags; |
| size_t tsp_size; |
| |
| if (dmxdev->demux->get_tsp_size) |
| tsp_size = dmxdev->demux->get_tsp_size(dmxdev->demux); |
| else |
| tsp_size = 188; |
| |
| /* |
| * For backward compatibility, default assumes that |
| * external only buffers are not supported. |
| */ |
| flags = 0; |
| if (dmxdev->demux->get_caps) { |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| |
| if (tsp_size == 188) |
| flags = caps.playback_188_tsp.flags; |
| else |
| flags = caps.playback_192_tsp.flags; |
| } |
| |
| if (!(flags & DMX_BUFFER_INTERNAL_SUPPORT) && |
| (flags & DMX_BUFFER_EXTERNAL_SUPPORT)) |
| is_external_only = 1; |
| else |
| is_external_only = 0; |
| |
| return is_external_only; |
| } |
| |
| static int dvb_dvr_verify_buffer_size(struct dmxdev *dmxdev, |
| unsigned int f_flags, |
| unsigned long size) |
| { |
| struct dmx_caps caps; |
| int tsp_size; |
| |
| if (!dmxdev->demux->get_caps) |
| return 1; |
| |
| if (dmxdev->demux->get_tsp_size) |
| tsp_size = dmxdev->demux->get_tsp_size(dmxdev->demux); |
| else |
| tsp_size = 188; |
| |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| if ((f_flags & O_ACCMODE) == O_RDONLY) |
| return (tsp_size == 188 && dvb_dmxdev_verify_buffer_size(size, |
| caps.recording_188_tsp.max_size, |
| caps.recording_188_tsp.size_alignment)) || |
| (tsp_size == 192 && dvb_dmxdev_verify_buffer_size(size, |
| caps.recording_192_tsp.max_size, |
| caps.recording_192_tsp.size_alignment)); |
| |
| return (tsp_size == 188 && dvb_dmxdev_verify_buffer_size(size, |
| caps.playback_188_tsp.max_size, |
| caps.playback_188_tsp.size_alignment)) || |
| (tsp_size == 192 && dvb_dmxdev_verify_buffer_size(size, |
| caps.playback_192_tsp.max_size, |
| caps.playback_192_tsp.size_alignment)); |
| } |
| |
| 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; |
| struct dvb_ringbuffer *src = &dmxdev->dvr_input_buffer; |
| struct dvb_ringbuffer *cmdbuf = &dmxdev->dvr_cmd_buffer; |
| int ret; |
| size_t todo; |
| ssize_t free_space; |
| |
| if (!dmxdev->demux->write) |
| return -EOPNOTSUPP; |
| |
| if (!dvb_dvr_verify_buffer_size(dmxdev, file->f_flags, src->size) || |
| ((file->f_flags & O_ACCMODE) == O_RDONLY) || |
| !src->data || !cmdbuf->data || |
| (dvb_dvr_external_input_only(dmxdev) && |
| (dmxdev->dvr_input_buffer_mode == DMX_BUFFER_MODE_INTERNAL))) |
| return -EINVAL; |
| |
| if ((file->f_flags & O_NONBLOCK) && |
| (dvb_ringbuffer_free(src) == 0)) |
| return -EWOULDBLOCK; |
| |
| ret = 0; |
| for (todo = count; todo > 0; todo -= ret) { |
| ret = wait_event_interruptible(src->queue, |
| (dvb_ringbuffer_free(src)) || |
| !src->data || !cmdbuf->data || |
| (src->error != 0) || dmxdev->dvr_in_exit); |
| |
| if (ret < 0) |
| return ret; |
| |
| if (mutex_lock_interruptible(&dmxdev->mutex)) |
| return -ERESTARTSYS; |
| |
| if ((!src->data) || (!cmdbuf->data)) { |
| mutex_unlock(&dmxdev->mutex); |
| return 0; |
| } |
| |
| if (dmxdev->exit || dmxdev->dvr_in_exit) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ENODEV; |
| } |
| |
| if (src->error) { |
| ret = src->error; |
| dvb_ringbuffer_flush(src); |
| mutex_unlock(&dmxdev->mutex); |
| wake_up_all(&src->queue); |
| return ret; |
| } |
| |
| free_space = dvb_ringbuffer_free(src); |
| |
| if (free_space > todo) |
| free_space = todo; |
| |
| ret = dvb_ringbuffer_write_user(src, buf, free_space); |
| |
| if (ret < 0) { |
| mutex_unlock(&dmxdev->mutex); |
| return ret; |
| } |
| |
| buf += ret; |
| |
| dvb_dvr_queue_data_feed(dmxdev, ret); |
| |
| mutex_unlock(&dmxdev->mutex); |
| } |
| |
| return (count - todo) ? (count - todo) : ret; |
| } |
| |
| static int dvb_dmxdev_flush_data(struct dmxdev_filter *filter, size_t length) |
| { |
| int ret = 0; |
| unsigned long flags; |
| |
| struct dvb_ringbuffer *buffer = &filter->buffer; |
| struct dmxdev_events_queue *events = &filter->events; |
| |
| if (filter->type == DMXDEV_TYPE_PES && |
| filter->params.pes.output == DMX_OUT_TS_TAP) { |
| buffer = &filter->dev->dvr_buffer; |
| events = &filter->dev->dvr_output_events; |
| } |
| |
| /* |
| * Drop 'length' pending data bytes from the ringbuffer and update |
| * event queue accordingly, similarly to dvb_dmxdev_release_data(). |
| */ |
| spin_lock_irqsave(&filter->dev->lock, flags); |
| DVB_RINGBUFFER_SKIP(buffer, length); |
| buffer->error = 0; |
| dvb_dmxdev_flush_events(events); |
| events->current_event_start_offset = buffer->pwrite; |
| spin_unlock_irqrestore(&filter->dev->lock, flags); |
| |
| if (filter->type == DMXDEV_TYPE_PES) { |
| struct dmxdev_feed *feed; |
| |
| feed = list_first_entry(&filter->feed.ts, |
| struct dmxdev_feed, next); |
| |
| if (feed->ts->flush_buffer) |
| return feed->ts->flush_buffer(feed->ts, length); |
| } else if (filter->type == DMXDEV_TYPE_SEC && |
| filter->feed.sec.feed->flush_buffer) { |
| return filter->feed.sec.feed->flush_buffer( |
| filter->feed.sec.feed, length); |
| } |
| |
| return ret; |
| } |
| |
| static inline void dvb_dmxdev_auto_flush_buffer(struct dmxdev_filter *filter, |
| struct dvb_ringbuffer *buf) |
| { |
| size_t flush_len; |
| |
| /* |
| * When buffer overflowed, demux-dev marked the buffer in |
| * error state. If auto-flush is enabled discard current |
| * pending data in buffer. |
| */ |
| if (overflow_auto_flush) { |
| flush_len = dvb_ringbuffer_avail(buf); |
| dvb_dmxdev_flush_data(filter, flush_len); |
| } |
| } |
| |
| static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, |
| loff_t *ppos) |
| { |
| ssize_t res; |
| struct dvb_device *dvbdev = file->private_data; |
| struct dmxdev *dmxdev = dvbdev->priv; |
| unsigned long flags; |
| |
| if (dmxdev->exit) |
| return -ENODEV; |
| |
| if (!dvb_dvr_verify_buffer_size(dmxdev, file->f_flags, |
| dmxdev->dvr_buffer.size)) |
| return -EINVAL; |
| |
| res = dvb_dmxdev_buffer_read(NULL, &dmxdev->dvr_buffer, |
| file->f_flags & O_NONBLOCK, |
| buf, count, ppos); |
| |
| if (res > 0) { |
| dvb_dmxdev_notify_data_read(dmxdev->dvr_feed, res); |
| spin_lock_irqsave(&dmxdev->lock, flags); |
| dvb_dmxdev_update_events(&dmxdev->dvr_output_events, res); |
| spin_unlock_irqrestore(&dmxdev->lock, flags); |
| |
| /* |
| * in PULL mode, we might be stalling on |
| * event queue, so need to wake-up waiters |
| */ |
| if (dmxdev->playback_mode == DMX_PB_MODE_PULL) |
| wake_up_all(&dmxdev->dvr_buffer.queue); |
| } else if (res == -EOVERFLOW) { |
| dvb_dmxdev_auto_flush_buffer(dmxdev->dvr_feed, |
| &dmxdev->dvr_buffer); |
| } |
| |
| return res; |
| } |
| |
| /* |
| * dvb_dvr_push_oob_cmd |
| * |
| * Note: this function assume dmxdev->mutex was taken, so command buffer cannot |
| * be released during its operation. |
| */ |
| static int dvb_dvr_push_oob_cmd(struct dmxdev *dmxdev, unsigned int f_flags, |
| struct dmx_oob_command *cmd) |
| { |
| struct dvb_ringbuffer *cmdbuf = &dmxdev->dvr_cmd_buffer; |
| struct dvr_command *dvr_cmd; |
| |
| if ((f_flags & O_ACCMODE) == O_RDONLY || |
| dmxdev->source < DMX_SOURCE_DVR0) |
| return -EPERM; |
| |
| if (dvb_ringbuffer_free(cmdbuf) < sizeof(*dvr_cmd)) |
| return -ENOMEM; |
| |
| dvr_cmd = (struct dvr_command *)&cmdbuf->data[cmdbuf->pwrite]; |
| dvr_cmd->type = DVR_OOB_CMD; |
| dvr_cmd->cmd.oobcmd = *cmd; |
| DVB_RINGBUFFER_PUSH(cmdbuf, sizeof(*dvr_cmd)); |
| wake_up_all(&cmdbuf->queue); |
| |
| return 0; |
| } |
| |
| static int dvb_dvr_flush_buffer(struct dmxdev *dmxdev, unsigned int f_flags) |
| { |
| size_t flush_len; |
| int ret; |
| |
| if ((f_flags & O_ACCMODE) != O_RDONLY) |
| return -EINVAL; |
| |
| flush_len = dvb_ringbuffer_avail(&dmxdev->dvr_buffer); |
| ret = dvb_dmxdev_flush_data(dmxdev->dvr_feed, flush_len); |
| |
| return ret; |
| } |
| |
| static int dvb_dvr_set_buffer_size(struct dmxdev *dmxdev, |
| unsigned int f_flags, |
| unsigned long size) |
| { |
| struct dvb_ringbuffer *buf; |
| void *newmem; |
| void *oldmem; |
| spinlock_t *lock; |
| enum dmx_buffer_mode buffer_mode; |
| |
| pr_debug("function : %s\n", __func__); |
| |
| if ((f_flags & O_ACCMODE) == O_RDONLY) { |
| buf = &dmxdev->dvr_buffer; |
| lock = &dmxdev->lock; |
| buffer_mode = dmxdev->dvr_buffer_mode; |
| } else { |
| buf = &dmxdev->dvr_input_buffer; |
| lock = &dmxdev->dvr_in_lock; |
| buffer_mode = dmxdev->dvr_input_buffer_mode; |
| } |
| |
| if (buf->size == size) |
| return 0; |
| if (!size || (buffer_mode == DMX_BUFFER_MODE_EXTERNAL)) |
| return -EINVAL; |
| |
| newmem = vmalloc_user(size); |
| if (!newmem) |
| return -ENOMEM; |
| |
| oldmem = buf->data; |
| |
| spin_lock_irq(lock); |
| |
| if (((f_flags & O_ACCMODE) != O_RDONLY) && |
| (dmxdev->dvr_processing_input)) { |
| spin_unlock_irq(lock); |
| vfree(oldmem); |
| return -EBUSY; |
| } |
| |
| buf->data = newmem; |
| buf->size = size; |
| |
| /* reset and not flush in case the buffer shrinks */ |
| dvb_ringbuffer_reset(buf); |
| |
| spin_unlock_irq(lock); |
| |
| vfree(oldmem); |
| |
| return 0; |
| } |
| |
| static int dvb_dvr_set_buffer_mode(struct dmxdev *dmxdev, |
| unsigned int f_flags, enum dmx_buffer_mode mode) |
| { |
| struct dvb_ringbuffer *buf; |
| spinlock_t *lock; |
| enum dmx_buffer_mode *buffer_mode; |
| void **buff_handle; |
| void *oldmem; |
| int *is_protected; |
| |
| if ((mode != DMX_BUFFER_MODE_INTERNAL) && |
| (mode != DMX_BUFFER_MODE_EXTERNAL)) |
| return -EINVAL; |
| |
| if ((mode == DMX_BUFFER_MODE_EXTERNAL) && |
| (!dmxdev->demux->map_buffer || !dmxdev->demux->unmap_buffer)) |
| return -EINVAL; |
| |
| if ((f_flags & O_ACCMODE) == O_RDONLY) { |
| buf = &dmxdev->dvr_buffer; |
| lock = &dmxdev->lock; |
| buffer_mode = &dmxdev->dvr_buffer_mode; |
| buff_handle = &dmxdev->dvr_priv_buff_handle; |
| is_protected = NULL; |
| } else { |
| buf = &dmxdev->dvr_input_buffer; |
| lock = &dmxdev->dvr_in_lock; |
| buffer_mode = &dmxdev->dvr_input_buffer_mode; |
| buff_handle = &dmxdev->demux->dvr_input.priv_handle; |
| is_protected = &dmxdev->demux->dvr_input_protected; |
| } |
| |
| if (mode == *buffer_mode) |
| return 0; |
| |
| oldmem = buf->data; |
| spin_lock_irq(lock); |
| buf->data = NULL; |
| spin_unlock_irq(lock); |
| |
| *buffer_mode = mode; |
| |
| if (mode == DMX_BUFFER_MODE_INTERNAL) { |
| /* switched from external to internal */ |
| if (*buff_handle) { |
| dmxdev->demux->unmap_buffer(dmxdev->demux, |
| *buff_handle); |
| *buff_handle = NULL; |
| } |
| |
| if (is_protected) |
| *is_protected = 0; |
| |
| /* set default internal buffer */ |
| dvb_dvr_set_buffer_size(dmxdev, f_flags, DVR_BUFFER_SIZE); |
| } else if (oldmem) { |
| /* switched from internal to external */ |
| vfree(oldmem); |
| } |
| |
| return 0; |
| } |
| |
| static int dvb_dvr_set_buffer(struct dmxdev *dmxdev, |
| unsigned int f_flags, struct dmx_buffer *dmx_buffer) |
| { |
| struct dvb_ringbuffer *buf; |
| spinlock_t *lock; |
| enum dmx_buffer_mode buffer_mode; |
| void **buff_handle; |
| void *newmem; |
| void *oldmem; |
| int *is_protected; |
| struct dmx_caps caps; |
| |
| if (dmxdev->demux->get_caps) |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| else |
| caps.caps = 0; |
| |
| if ((f_flags & O_ACCMODE) == O_RDONLY) { |
| buf = &dmxdev->dvr_buffer; |
| lock = &dmxdev->lock; |
| buffer_mode = dmxdev->dvr_buffer_mode; |
| buff_handle = &dmxdev->dvr_priv_buff_handle; |
| is_protected = NULL; |
| } else { |
| buf = &dmxdev->dvr_input_buffer; |
| lock = &dmxdev->dvr_in_lock; |
| buffer_mode = dmxdev->dvr_input_buffer_mode; |
| buff_handle = &dmxdev->demux->dvr_input.priv_handle; |
| is_protected = &dmxdev->demux->dvr_input_protected; |
| if (!(caps.caps & DMX_CAP_SECURED_INPUT_PLAYBACK) && |
| dmx_buffer->is_protected) |
| return -EINVAL; |
| } |
| |
| if (!dmx_buffer->size || |
| (buffer_mode == DMX_BUFFER_MODE_INTERNAL)) |
| return -EINVAL; |
| |
| oldmem = *buff_handle; |
| |
| /* |
| * Protected buffer is relevant only for DVR input buffer |
| * when DVR device is opened for write. In such case, |
| * buffer is mapped only if the buffer is not protected one. |
| */ |
| if (!is_protected || !dmx_buffer->is_protected) { |
| if (dmxdev->demux->map_buffer(dmxdev->demux, dmx_buffer, |
| buff_handle, &newmem)) |
| return -ENOMEM; |
| } else { |
| newmem = NULL; |
| *buff_handle = NULL; |
| } |
| |
| spin_lock_irq(lock); |
| buf->data = newmem; |
| buf->size = dmx_buffer->size; |
| if (is_protected) |
| *is_protected = dmx_buffer->is_protected; |
| dvb_ringbuffer_reset(buf); |
| spin_unlock_irq(lock); |
| |
| if (oldmem) |
| dmxdev->demux->unmap_buffer(dmxdev->demux, oldmem); |
| |
| return 0; |
| } |
| |
| static int dvb_dvr_get_event(struct dmxdev *dmxdev, |
| unsigned int f_flags, |
| struct dmx_filter_event *event) |
| { |
| int res = 0; |
| |
| if (!((f_flags & O_ACCMODE) == O_RDONLY)) |
| return -EINVAL; |
| |
| spin_lock_irq(&dmxdev->lock); |
| |
| if (dmxdev->dvr_buffer.error == -EOVERFLOW) { |
| event->type = DMX_EVENT_BUFFER_OVERFLOW; |
| dmxdev->dvr_buffer.error = 0; |
| } else { |
| res = dvb_dmxdev_remove_event(&dmxdev->dvr_output_events, |
| event); |
| if (res) { |
| spin_unlock_irq(&dmxdev->lock); |
| return res; |
| } |
| } |
| |
| spin_unlock_irq(&dmxdev->lock); |
| |
| if (event->type == DMX_EVENT_BUFFER_OVERFLOW) |
| dvb_dmxdev_auto_flush_buffer(dmxdev->dvr_feed, |
| &dmxdev->dvr_buffer); |
| |
| /* |
| * in PULL mode, we might be stalling on |
| * event queue, so need to wake-up waiters |
| */ |
| if (dmxdev->playback_mode == DMX_PB_MODE_PULL) |
| wake_up_all(&dmxdev->dvr_buffer.queue); |
| |
| return res; |
| } |
| |
| static int dvb_dvr_get_buffer_status(struct dmxdev *dmxdev, |
| unsigned int f_flags, |
| struct dmx_buffer_status *dmx_buffer_status) |
| { |
| struct dvb_ringbuffer *buf; |
| spinlock_t *lock; |
| |
| if ((f_flags & O_ACCMODE) == O_RDONLY) { |
| buf = &dmxdev->dvr_buffer; |
| lock = &dmxdev->lock; |
| } else { |
| buf = &dmxdev->dvr_input_buffer; |
| lock = &dmxdev->dvr_in_lock; |
| } |
| |
| spin_lock_irq(lock); |
| |
| dmx_buffer_status->error = buf->error; |
| dmx_buffer_status->fullness = dvb_ringbuffer_avail(buf); |
| dmx_buffer_status->free_bytes = dvb_ringbuffer_free(buf); |
| dmx_buffer_status->read_offset = buf->pread; |
| dmx_buffer_status->write_offset = buf->pwrite; |
| dmx_buffer_status->size = buf->size; |
| buf->error = 0; |
| |
| spin_unlock_irq(lock); |
| |
| if (dmx_buffer_status->error == -EOVERFLOW) |
| dvb_dmxdev_auto_flush_buffer(dmxdev->dvr_feed, buf); |
| |
| return 0; |
| } |
| |
| static int dvb_dvr_release_data(struct dmxdev *dmxdev, |
| unsigned int f_flags, |
| u32 bytes_count) |
| { |
| ssize_t buff_fullness; |
| |
| if (!((f_flags & O_ACCMODE) == O_RDONLY)) |
| return -EINVAL; |
| |
| if (!bytes_count) |
| return 0; |
| |
| buff_fullness = dvb_ringbuffer_avail(&dmxdev->dvr_buffer); |
| |
| if (bytes_count > buff_fullness) |
| return -EINVAL; |
| |
| DVB_RINGBUFFER_SKIP(&dmxdev->dvr_buffer, bytes_count); |
| |
| dvb_dmxdev_notify_data_read(dmxdev->dvr_feed, bytes_count); |
| spin_lock_irq(&dmxdev->lock); |
| dvb_dmxdev_update_events(&dmxdev->dvr_output_events, bytes_count); |
| spin_unlock_irq(&dmxdev->lock); |
| |
| wake_up_all(&dmxdev->dvr_buffer.queue); |
| return 0; |
| } |
| |
| /* |
| * dvb_dvr_feed_data - Notify new data in DVR input buffer |
| * |
| * @dmxdev - demux device instance |
| * @f_flags - demux device file flag (access mode) |
| * @bytes_count - how many bytes were written to the input buffer |
| * |
| * Note: this function assume dmxdev->mutex was taken, so buffer cannot |
| * be released during its operation. |
| */ |
| static int dvb_dvr_feed_data(struct dmxdev *dmxdev, |
| unsigned int f_flags, |
| u32 bytes_count) |
| { |
| ssize_t free_space; |
| struct dvb_ringbuffer *buffer = &dmxdev->dvr_input_buffer; |
| |
| if ((f_flags & O_ACCMODE) == O_RDONLY) |
| return -EINVAL; |
| |
| if (!bytes_count) |
| return 0; |
| |
| free_space = dvb_ringbuffer_free(buffer); |
| |
| if (bytes_count > free_space) |
| return -EINVAL; |
| |
| DVB_RINGBUFFER_PUSH(buffer, bytes_count); |
| |
| dvb_dvr_queue_data_feed(dmxdev, bytes_count); |
| |
| 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 || |
| (dmxdevfilter->buffer_mode == DMX_BUFFER_MODE_EXTERNAL)) |
| return -EINVAL; |
| if (dmxdevfilter->state >= DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| newmem = vmalloc_user(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 int dvb_dmxdev_set_buffer_mode(struct dmxdev_filter *dmxdevfilter, |
| enum dmx_buffer_mode mode) |
| { |
| struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; |
| struct dmxdev *dmxdev = dmxdevfilter->dev; |
| void *oldmem; |
| |
| if (dmxdevfilter->state >= DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| if ((mode != DMX_BUFFER_MODE_INTERNAL) && |
| (mode != DMX_BUFFER_MODE_EXTERNAL)) |
| return -EINVAL; |
| |
| if ((mode == DMX_BUFFER_MODE_EXTERNAL) && |
| (!dmxdev->demux->map_buffer || !dmxdev->demux->unmap_buffer)) |
| return -EINVAL; |
| |
| if (mode == dmxdevfilter->buffer_mode) |
| return 0; |
| |
| oldmem = buf->data; |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| buf->data = NULL; |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| dmxdevfilter->buffer_mode = mode; |
| |
| if (mode == DMX_BUFFER_MODE_INTERNAL) { |
| /* switched from external to internal */ |
| if (dmxdevfilter->priv_buff_handle) { |
| dmxdev->demux->unmap_buffer(dmxdev->demux, |
| dmxdevfilter->priv_buff_handle); |
| dmxdevfilter->priv_buff_handle = NULL; |
| } |
| } else if (oldmem) { |
| /* switched from internal to external */ |
| vfree(oldmem); |
| } |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_set_buffer(struct dmxdev_filter *dmxdevfilter, |
| struct dmx_buffer *buffer) |
| { |
| struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; |
| struct dmxdev *dmxdev = dmxdevfilter->dev; |
| void *newmem; |
| void *oldmem; |
| |
| if (dmxdevfilter->state >= DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| if ((!buffer->size) || |
| (dmxdevfilter->buffer_mode == DMX_BUFFER_MODE_INTERNAL)) |
| return -EINVAL; |
| |
| oldmem = dmxdevfilter->priv_buff_handle; |
| if (dmxdev->demux->map_buffer(dmxdev->demux, buffer, |
| &dmxdevfilter->priv_buff_handle, &newmem)) |
| return -ENOMEM; |
| |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| buf->data = newmem; |
| buf->size = buffer->size; |
| dvb_ringbuffer_reset(buf); |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| if (oldmem) |
| dmxdev->demux->unmap_buffer(dmxdev->demux, oldmem); |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_set_tsp_out_format(struct dmxdev_filter *dmxdevfilter, |
| enum dmx_tsp_format_t dmx_tsp_format) |
| { |
| if (dmxdevfilter->state >= DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| if ((dmx_tsp_format > DMX_TSP_FORMAT_192_HEAD) || |
| (dmx_tsp_format < DMX_TSP_FORMAT_188)) |
| return -EINVAL; |
| |
| dmxdevfilter->dmx_tsp_format = dmx_tsp_format; |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_set_decoder_buffer_size( |
| struct dmxdev_filter *dmxdevfilter, |
| unsigned long size) |
| { |
| struct dmx_caps caps; |
| struct dmx_demux *demux = dmxdevfilter->dev->demux; |
| |
| if (demux->get_caps) { |
| demux->get_caps(demux, &caps); |
| if (!dvb_dmxdev_verify_buffer_size(size, caps.decoder.max_size, |
| caps.decoder.size_alignment)) |
| return -EINVAL; |
| } |
| |
| if (size == 0) |
| return -EINVAL; |
| |
| if (dmxdevfilter->decoder_buffers.buffers_size == size) |
| return 0; |
| |
| if (dmxdevfilter->state >= DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| /* |
| * In case decoder buffers were already set before to some external |
| * buffers, setting the decoder buffer size alone implies transition |
| * to internal buffer mode. |
| */ |
| dmxdevfilter->decoder_buffers.buffers_size = size; |
| dmxdevfilter->decoder_buffers.buffers_num = 0; |
| dmxdevfilter->decoder_buffers.is_linear = 0; |
| return 0; |
| } |
| |
| static int dvb_dmxdev_set_source(struct dmxdev_filter *dmxdevfilter, |
| dmx_source_t *source) |
| { |
| int ret = 0; |
| struct dmxdev *dev; |
| |
| if (dmxdevfilter->state == DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| dev = dmxdevfilter->dev; |
| if (dev->demux->set_source) |
| ret = dev->demux->set_source(dev->demux, source); |
| |
| if (!ret) |
| dev->source = *source; |
| |
| return ret; |
| } |
| |
| static int dvb_dmxdev_reuse_decoder_buf(struct dmxdev_filter *dmxdevfilter, |
| int cookie) |
| { |
| struct dmxdev_feed *feed; |
| |
| if (dmxdevfilter->state != DMXDEV_STATE_GO || |
| (dmxdevfilter->type != DMXDEV_TYPE_PES) || |
| (dmxdevfilter->params.pes.output != DMX_OUT_DECODER) || |
| (dmxdevfilter->events.event_mask.disable_mask & |
| DMX_EVENT_NEW_ES_DATA)) |
| return -EPERM; |
| |
| /* Only one feed should be in the list in case of decoder */ |
| feed = list_first_entry(&dmxdevfilter->feed.ts, |
| struct dmxdev_feed, next); |
| if (feed && feed->ts && feed->ts->reuse_decoder_buffer) |
| return feed->ts->reuse_decoder_buffer(feed->ts, cookie); |
| |
| return -ENODEV; |
| } |
| |
| static int dvb_dmxdev_set_event_mask(struct dmxdev_filter *dmxdevfilter, |
| struct dmx_events_mask *event_mask) |
| { |
| if (!event_mask || |
| (event_mask->wakeup_threshold >= DMX_EVENT_QUEUE_SIZE)) |
| return -EINVAL; |
| |
| if (dmxdevfilter->state == DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| /* |
| * Overflow event is not allowed to be masked. |
| * This is because if overflow occurs, demux stops outputting data |
| * until user is notified. If user is using events to read the data, |
| * the overflow event must be always enabled or otherwise we would |
| * never recover from overflow state. |
| */ |
| event_mask->disable_mask &= ~(u32)DMX_EVENT_BUFFER_OVERFLOW; |
| event_mask->no_wakeup_mask &= ~(u32)DMX_EVENT_BUFFER_OVERFLOW; |
| |
| dmxdevfilter->events.event_mask = *event_mask; |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_get_event_mask(struct dmxdev_filter *dmxdevfilter, |
| struct dmx_events_mask *event_mask) |
| { |
| if (!event_mask) |
| return -EINVAL; |
| |
| *event_mask = dmxdevfilter->events.event_mask; |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_set_indexing_params(struct dmxdev_filter *dmxdevfilter, |
| struct dmx_indexing_params *idx_params) |
| { |
| int found_pid; |
| struct dmxdev_feed *feed; |
| struct dmxdev_feed *ts_feed = NULL; |
| struct dmx_caps caps; |
| int ret = 0; |
| |
| if (!dmxdevfilter->dev->demux->get_caps) |
| return -EINVAL; |
| |
| dmxdevfilter->dev->demux->get_caps(dmxdevfilter->dev->demux, &caps); |
| |
| if (!idx_params || |
| !(caps.caps & DMX_CAP_VIDEO_INDEXING) || |
| (dmxdevfilter->state < DMXDEV_STATE_SET) || |
| (dmxdevfilter->type != DMXDEV_TYPE_PES) || |
| ((dmxdevfilter->params.pes.output != DMX_OUT_TS_TAP) && |
| (dmxdevfilter->params.pes.output != DMX_OUT_TSDEMUX_TAP))) |
| return -EINVAL; |
| |
| if (idx_params->enable && !idx_params->types) |
| return -EINVAL; |
| |
| found_pid = 0; |
| list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) { |
| if (feed->pid == idx_params->pid) { |
| found_pid = 1; |
| ts_feed = feed; |
| ts_feed->idx_params = *idx_params; |
| if ((dmxdevfilter->state == DMXDEV_STATE_GO) && |
| ts_feed->ts->set_idx_params) |
| ret = ts_feed->ts->set_idx_params( |
| ts_feed->ts, idx_params); |
| break; |
| } |
| } |
| |
| if (!found_pid) |
| return -EINVAL; |
| |
| return ret; |
| } |
| |
| static int dvb_dmxdev_get_scrambling_bits(struct dmxdev_filter *filter, |
| struct dmx_scrambling_bits *scrambling_bits) |
| { |
| struct dmxdev_feed *feed; |
| |
| if (!scrambling_bits || |
| (filter->state != DMXDEV_STATE_GO)) |
| return -EINVAL; |
| |
| if (filter->type == DMXDEV_TYPE_SEC) { |
| if (filter->feed.sec.feed->get_scrambling_bits) |
| return filter->feed.sec.feed->get_scrambling_bits( |
| filter->feed.sec.feed, |
| &scrambling_bits->value); |
| return -EINVAL; |
| } |
| |
| list_for_each_entry(feed, &filter->feed.ts, next) { |
| if (feed->pid == scrambling_bits->pid) { |
| if (feed->ts->get_scrambling_bits) |
| return feed->ts->get_scrambling_bits(feed->ts, |
| &scrambling_bits->value); |
| return -EINVAL; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static void dvb_dmxdev_ts_insertion_work(struct work_struct *worker) |
| { |
| struct ts_insertion_buffer *ts_buffer = |
| container_of(to_delayed_work(worker), |
| struct ts_insertion_buffer, dwork); |
| struct dmxdev_feed *feed; |
| size_t free_bytes; |
| struct dmx_ts_feed *ts; |
| |
| mutex_lock(&ts_buffer->dmxdevfilter->mutex); |
| |
| if (ts_buffer->abort || |
| (ts_buffer->dmxdevfilter->state != DMXDEV_STATE_GO)) { |
| mutex_unlock(&ts_buffer->dmxdevfilter->mutex); |
| return; |
| } |
| |
| feed = list_first_entry(&ts_buffer->dmxdevfilter->feed.ts, |
| struct dmxdev_feed, next); |
| ts = feed->ts; |
| free_bytes = dvb_ringbuffer_free(&ts_buffer->dmxdevfilter->buffer); |
| |
| mutex_unlock(&ts_buffer->dmxdevfilter->mutex); |
| |
| if (ts_buffer->size < free_bytes) |
| ts->ts_insertion_insert_buffer(ts, |
| ts_buffer->buffer, ts_buffer->size); |
| |
| if (ts_buffer->repetition_time && !ts_buffer->abort) |
| schedule_delayed_work(&ts_buffer->dwork, |
| msecs_to_jiffies(ts_buffer->repetition_time)); |
| } |
| |
| static void dvb_dmxdev_queue_ts_insertion( |
| struct ts_insertion_buffer *ts_buffer) |
| { |
| size_t tsp_size; |
| |
| if (ts_buffer->dmxdevfilter->dmx_tsp_format == DMX_TSP_FORMAT_188) |
| tsp_size = 188; |
| else |
| tsp_size = 192; |
| |
| if (ts_buffer->size % tsp_size) { |
| pr_err("%s: Wrong buffer alignment, size=%zu, tsp_size=%zu\n", |
| __func__, ts_buffer->size, tsp_size); |
| return; |
| } |
| |
| ts_buffer->abort = 0; |
| schedule_delayed_work(&ts_buffer->dwork, 0); |
| } |
| |
| static void dvb_dmxdev_cancel_ts_insertion( |
| struct ts_insertion_buffer *ts_buffer) |
| { |
| /* |
| * This function assumes it is called while mutex |
| * of demux filter is taken. Since work in workqueue |
| * captures the filter's mutex to protect against the DB, |
| * mutex needs to be released before waiting for the work |
| * to get finished otherwise work in workqueue will |
| * never be finished. |
| */ |
| if (!mutex_is_locked(&ts_buffer->dmxdevfilter->mutex)) { |
| pr_err("%s: mutex is not locked!\n", __func__); |
| return; |
| } |
| |
| ts_buffer->abort = 1; |
| |
| mutex_unlock(&ts_buffer->dmxdevfilter->mutex); |
| cancel_delayed_work_sync(&ts_buffer->dwork); |
| mutex_lock(&ts_buffer->dmxdevfilter->mutex); |
| } |
| |
| static int dvb_dmxdev_set_ts_insertion(struct dmxdev_filter *dmxdevfilter, |
| struct dmx_set_ts_insertion *params) |
| { |
| int ret = 0; |
| int first_buffer; |
| struct dmxdev_feed *feed; |
| struct ts_insertion_buffer *ts_buffer; |
| struct dmx_caps caps; |
| |
| if (!dmxdevfilter->dev->demux->get_caps) |
| return -EINVAL; |
| |
| dmxdevfilter->dev->demux->get_caps(dmxdevfilter->dev->demux, &caps); |
| |
| if (!params || |
| !params->size || |
| !(caps.caps & DMX_CAP_TS_INSERTION) || |
| (dmxdevfilter->state < DMXDEV_STATE_SET) || |
| (dmxdevfilter->type != DMXDEV_TYPE_PES) || |
| ((dmxdevfilter->params.pes.output != DMX_OUT_TS_TAP) && |
| (dmxdevfilter->params.pes.output != DMX_OUT_TSDEMUX_TAP))) |
| return -EINVAL; |
| |
| ts_buffer = vmalloc(sizeof(struct ts_insertion_buffer)); |
| if (!ts_buffer) |
| return -ENOMEM; |
| |
| ts_buffer->buffer = vmalloc(params->size); |
| if (!ts_buffer->buffer) { |
| vfree(ts_buffer); |
| return -ENOMEM; |
| } |
| |
| if (copy_from_user(ts_buffer->buffer, |
| params->ts_packets, params->size)) { |
| vfree(ts_buffer->buffer); |
| vfree(ts_buffer); |
| return -EFAULT; |
| } |
| |
| if (params->repetition_time && |
| params->repetition_time < DMX_MIN_INSERTION_REPETITION_TIME) |
| params->repetition_time = DMX_MIN_INSERTION_REPETITION_TIME; |
| |
| ts_buffer->size = params->size; |
| ts_buffer->identifier = params->identifier; |
| ts_buffer->repetition_time = params->repetition_time; |
| ts_buffer->dmxdevfilter = dmxdevfilter; |
| INIT_DELAYED_WORK(&ts_buffer->dwork, dvb_dmxdev_ts_insertion_work); |
| |
| first_buffer = list_empty(&dmxdevfilter->insertion_buffers); |
| list_add_tail(&ts_buffer->next, &dmxdevfilter->insertion_buffers); |
| |
| if (dmxdevfilter->state != DMXDEV_STATE_GO) |
| return 0; |
| |
| feed = list_first_entry(&dmxdevfilter->feed.ts, |
| struct dmxdev_feed, next); |
| |
| if (first_buffer && feed->ts->ts_insertion_init) |
| ret = feed->ts->ts_insertion_init(feed->ts); |
| |
| if (!ret) { |
| dvb_dmxdev_queue_ts_insertion(ts_buffer); |
| } else { |
| list_del(&ts_buffer->next); |
| vfree(ts_buffer->buffer); |
| vfree(ts_buffer); |
| } |
| |
| return ret; |
| } |
| |
| static int dvb_dmxdev_abort_ts_insertion(struct dmxdev_filter *dmxdevfilter, |
| struct dmx_abort_ts_insertion *params) |
| { |
| int ret = 0; |
| int found_buffer; |
| struct dmxdev_feed *feed; |
| struct ts_insertion_buffer *ts_buffer, *tmp; |
| struct dmx_caps caps; |
| |
| if (!dmxdevfilter->dev->demux->get_caps) |
| return -EINVAL; |
| |
| dmxdevfilter->dev->demux->get_caps(dmxdevfilter->dev->demux, &caps); |
| |
| if (!params || |
| !(caps.caps & DMX_CAP_TS_INSERTION) || |
| (dmxdevfilter->state < DMXDEV_STATE_SET) || |
| (dmxdevfilter->type != DMXDEV_TYPE_PES) || |
| ((dmxdevfilter->params.pes.output != DMX_OUT_TS_TAP) && |
| (dmxdevfilter->params.pes.output != DMX_OUT_TSDEMUX_TAP))) |
| return -EINVAL; |
| |
| found_buffer = 0; |
| list_for_each_entry_safe(ts_buffer, tmp, |
| &dmxdevfilter->insertion_buffers, next) { |
| if (ts_buffer->identifier == params->identifier) { |
| list_del(&ts_buffer->next); |
| found_buffer = 1; |
| break; |
| } |
| } |
| |
| if (!found_buffer) |
| return -EINVAL; |
| |
| if (dmxdevfilter->state == DMXDEV_STATE_GO) { |
| dvb_dmxdev_cancel_ts_insertion(ts_buffer); |
| if (list_empty(&dmxdevfilter->insertion_buffers)) { |
| feed = list_first_entry(&dmxdevfilter->feed.ts, |
| struct dmxdev_feed, next); |
| if (feed->ts->ts_insertion_terminate) |
| ret = feed->ts->ts_insertion_terminate( |
| feed->ts); |
| } |
| } |
| |
| vfree(ts_buffer->buffer); |
| vfree(ts_buffer); |
| |
| return ret; |
| } |
| |
| static int dvb_dmxdev_ts_fullness_callback(struct dmx_ts_feed *filter, |
| int required_space, int wait) |
| { |
| struct dmxdev_filter *dmxdevfilter = filter->priv; |
| struct dvb_ringbuffer *src; |
| struct dmxdev_events_queue *events; |
| int ret; |
| |
| if (!dmxdevfilter) { |
| pr_err("%s: NULL demux filter object!\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (dmxdevfilter->params.pes.output != DMX_OUT_TS_TAP) { |
| src = &dmxdevfilter->buffer; |
| events = &dmxdevfilter->events; |
| } else { |
| src = &dmxdevfilter->dev->dvr_buffer; |
| events = &dmxdevfilter->dev->dvr_output_events; |
| } |
| |
| do { |
| ret = 0; |
| |
| if (dmxdevfilter->dev->dvr_in_exit) |
| return -ENODEV; |
| |
| spin_lock(&dmxdevfilter->dev->lock); |
| |
| if ((!src->data) || |
| (dmxdevfilter->state != DMXDEV_STATE_GO)) |
| ret = -EINVAL; |
| else if (src->error) |
| ret = src->error; |
| |
| if (ret) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return ret; |
| } |
| |
| if ((required_space <= dvb_ringbuffer_free(src)) && |
| (!dvb_dmxdev_events_is_full(events))) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return 0; |
| } |
| |
| spin_unlock(&dmxdevfilter->dev->lock); |
| |
| if (!wait) |
| return -ENOSPC; |
| |
| ret = wait_event_interruptible(src->queue, |
| (!src->data) || |
| ((dvb_ringbuffer_free(src) >= required_space) && |
| (!dvb_dmxdev_events_is_full(events))) || |
| (src->error != 0) || |
| (dmxdevfilter->state != DMXDEV_STATE_GO) || |
| dmxdevfilter->dev->dvr_in_exit); |
| |
| if (ret < 0) |
| return ret; |
| } while (1); |
| } |
| |
| static int dvb_dmxdev_sec_fullness_callback( |
| struct dmx_section_filter *filter, |
| int required_space, int wait) |
| { |
| struct dmxdev_filter *dmxdevfilter = filter->priv; |
| struct dvb_ringbuffer *src; |
| struct dmxdev_events_queue *events; |
| int ret; |
| |
| if (!dmxdevfilter) { |
| pr_err("%s: NULL demux filter object!\n", __func__); |
| return -ENODEV; |
| } |
| |
| src = &dmxdevfilter->buffer; |
| events = &dmxdevfilter->events; |
| |
| do { |
| ret = 0; |
| |
| if (dmxdevfilter->dev->dvr_in_exit) |
| return -ENODEV; |
| |
| spin_lock(&dmxdevfilter->dev->lock); |
| |
| if ((!src->data) || |
| (dmxdevfilter->state != DMXDEV_STATE_GO)) |
| ret = -EINVAL; |
| else if (src->error) |
| ret = src->error; |
| |
| if (ret) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return ret; |
| } |
| |
| if ((required_space <= dvb_ringbuffer_free(src)) && |
| (!dvb_dmxdev_events_is_full(events))) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return 0; |
| } |
| |
| spin_unlock(&dmxdevfilter->dev->lock); |
| |
| if (!wait) |
| return -ENOSPC; |
| |
| ret = wait_event_interruptible(src->queue, |
| (!src->data) || |
| ((dvb_ringbuffer_free(src) >= required_space) && |
| (!dvb_dmxdev_events_is_full(events))) || |
| (src->error != 0) || |
| (dmxdevfilter->state != DMXDEV_STATE_GO) || |
| dmxdevfilter->dev->dvr_in_exit); |
| |
| if (ret < 0) |
| return ret; |
| } while (1); |
| } |
| |
| static int dvb_dmxdev_set_playback_mode(struct dmxdev_filter *dmxdevfilter, |
| enum dmx_playback_mode_t playback_mode) |
| { |
| struct dmxdev *dmxdev = dmxdevfilter->dev; |
| struct dmx_caps caps; |
| |
| if (dmxdev->demux->get_caps) |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| else |
| caps.caps = 0; |
| |
| if ((playback_mode != DMX_PB_MODE_PUSH) && |
| (playback_mode != DMX_PB_MODE_PULL)) |
| return -EINVAL; |
| |
| if (dmxdev->demux->set_playback_mode == NULL) |
| return -EINVAL; |
| |
| if (((dmxdev->source < DMX_SOURCE_DVR0) || |
| !(caps.caps & DMX_CAP_PULL_MODE)) && |
| (playback_mode == DMX_PB_MODE_PULL)) |
| return -EPERM; |
| |
| if (dmxdevfilter->state == DMXDEV_STATE_GO) |
| return -EBUSY; |
| |
| dmxdev->playback_mode = playback_mode; |
| |
| return dmxdev->demux->set_playback_mode( |
| dmxdev->demux, |
| dmxdev->playback_mode, |
| dvb_dmxdev_ts_fullness_callback, |
| dvb_dmxdev_sec_fullness_callback); |
| } |
| |
| static int dvb_dmxdev_flush_buffer(struct dmxdev_filter *filter) |
| { |
| size_t flush_len; |
| int ret; |
| |
| if (filter->state != DMXDEV_STATE_GO) |
| return -EINVAL; |
| |
| flush_len = dvb_ringbuffer_avail(&filter->buffer); |
| ret = dvb_dmxdev_flush_data(filter, flush_len); |
| |
| return ret; |
| } |
| |
| static int dvb_dmxdev_get_buffer_status( |
| struct dmxdev_filter *dmxdevfilter, |
| struct dmx_buffer_status *dmx_buffer_status) |
| { |
| struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; |
| |
| /* |
| * Note: Taking the dmxdevfilter->dev->lock spinlock is required only |
| * when getting the status of the Demux-userspace data ringbuffer . |
| * In case we are getting the status of a decoder buffer, taking this |
| * spinlock is not required and in fact might lead to a deadlock. |
| */ |
| if ((dmxdevfilter->type == DMXDEV_TYPE_PES) && |
| (dmxdevfilter->params.pes.output == DMX_OUT_DECODER)) { |
| struct dmxdev_feed *feed; |
| int ret; |
| |
| /* Only one feed should be in the list in case of decoder */ |
| feed = list_first_entry(&dmxdevfilter->feed.ts, |
| struct dmxdev_feed, next); |
| |
| /* Ask for status of decoder's buffer from underlying HW */ |
| if (feed->ts->get_decoder_buff_status) |
| ret = feed->ts->get_decoder_buff_status( |
| feed->ts, |
| dmx_buffer_status); |
| else |
| ret = -ENODEV; |
| |
| return ret; |
| } |
| |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| |
| if (!buf->data) { |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| return -EINVAL; |
| } |
| |
| dmx_buffer_status->error = buf->error; |
| dmx_buffer_status->fullness = dvb_ringbuffer_avail(buf); |
| dmx_buffer_status->free_bytes = dvb_ringbuffer_free(buf); |
| dmx_buffer_status->read_offset = buf->pread; |
| dmx_buffer_status->write_offset = buf->pwrite; |
| dmx_buffer_status->size = buf->size; |
| buf->error = 0; |
| |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| if (dmx_buffer_status->error == -EOVERFLOW) |
| dvb_dmxdev_auto_flush_buffer(dmxdevfilter, buf); |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_release_data(struct dmxdev_filter *dmxdevfilter, |
| u32 bytes_count) |
| { |
| ssize_t buff_fullness; |
| |
| if (!dmxdevfilter->buffer.data) |
| return -EINVAL; |
| |
| if (!bytes_count) |
| return 0; |
| |
| buff_fullness = dvb_ringbuffer_avail(&dmxdevfilter->buffer); |
| |
| if (bytes_count > buff_fullness) |
| return -EINVAL; |
| |
| DVB_RINGBUFFER_SKIP(&dmxdevfilter->buffer, bytes_count); |
| |
| dvb_dmxdev_notify_data_read(dmxdevfilter, bytes_count); |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| dvb_dmxdev_update_events(&dmxdevfilter->events, bytes_count); |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_get_event(struct dmxdev_filter *dmxdevfilter, |
| struct dmx_filter_event *event) |
| { |
| int res = 0; |
| |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| |
| /* Check first for filter overflow */ |
| if (dmxdevfilter->buffer.error == -EOVERFLOW) { |
| event->type = DMX_EVENT_BUFFER_OVERFLOW; |
| } else { |
| res = dvb_dmxdev_remove_event(&dmxdevfilter->events, event); |
| if (res) { |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| return res; |
| } |
| } |
| |
| /* clear buffer error now that user was notified */ |
| if (event->type == DMX_EVENT_BUFFER_OVERFLOW || |
| event->type == DMX_EVENT_SECTION_TIMEOUT) |
| dmxdevfilter->buffer.error = 0; |
| |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| if (event->type == DMX_EVENT_BUFFER_OVERFLOW) |
| dvb_dmxdev_auto_flush_buffer(dmxdevfilter, |
| &dmxdevfilter->buffer); |
| |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| |
| /* |
| * If no-data events are enabled on this filter, |
| * the events can be removed from the queue when |
| * user gets them. |
| * For filters with data events enabled, the event is removed |
| * from the queue only when the respective data is read. |
| */ |
| if (event->type != DMX_EVENT_BUFFER_OVERFLOW && |
| dmxdevfilter->events.data_read_event_masked) |
| dmxdevfilter->events.read_index = |
| dvb_dmxdev_advance_event_idx( |
| dmxdevfilter->events.read_index); |
| |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| /* |
| * in PULL mode, we might be stalling on |
| * event queue, so need to wake-up waiters |
| */ |
| if (dmxdevfilter->dev->playback_mode == DMX_PB_MODE_PULL) |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| |
| return res; |
| } |
| |
| static void dvb_dmxdev_filter_timeout(unsigned long data) |
| { |
| struct dmxdev_filter *dmxdevfilter = (struct dmxdev_filter *)data; |
| struct dmx_filter_event event; |
| |
| dmxdevfilter->buffer.error = -ETIMEDOUT; |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| dmxdevfilter->state = DMXDEV_STATE_TIMEDOUT; |
| event.type = DMX_EVENT_SECTION_TIMEOUT; |
| dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| wake_up_all(&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.function = dvb_dmxdev_filter_timeout; |
| dmxdevfilter->timer.data = (unsigned long)dmxdevfilter; |
| 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) |
| { |
| struct dmxdev_filter *dmxdevfilter = filter->priv; |
| struct dmx_filter_event event; |
| ssize_t free; |
| |
| |
| if (!dmxdevfilter) { |
| pr_err("%s: null filter.\n", __func__); |
| return -EINVAL; |
| } |
| |
| spin_lock(&dmxdevfilter->dev->lock); |
| |
| if (dmxdevfilter->buffer.error || |
| dmxdevfilter->state != DMXDEV_STATE_GO || |
| dmxdevfilter->eos_state) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return 0; |
| } |
| |
| /* Discard section data if event cannot be notified */ |
| if (!(dmxdevfilter->events.event_mask.disable_mask & |
| DMX_EVENT_NEW_SECTION) && |
| dvb_dmxdev_events_is_full(&dmxdevfilter->events)) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return 0; |
| } |
| |
| if ((buffer1_len + buffer2_len) == 0) { |
| if (buffer1 == NULL && buffer2 == NULL) { |
| /* Section was dropped due to CRC error */ |
| event.type = DMX_EVENT_SECTION_CRC_ERROR; |
| dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| } else { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| } |
| |
| return 0; |
| } |
| |
| event.params.section.base_offset = dmxdevfilter->buffer.pwrite; |
| event.params.section.start_offset = dmxdevfilter->buffer.pwrite; |
| |
| del_timer(&dmxdevfilter->timer); |
| |
| /* Verify output buffer has sufficient space, or report overflow */ |
| free = dvb_ringbuffer_free(&dmxdevfilter->buffer); |
| if (free < (buffer1_len + buffer2_len)) { |
| pr_debug("%s: section filter overflow (pid=%u)\n", |
| __func__, dmxdevfilter->params.sec.pid); |
| dmxdevfilter->buffer.error = -EOVERFLOW; |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| return 0; |
| } |
| |
| dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer1, buffer1_len); |
| dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer2, buffer2_len); |
| |
| event.type = DMX_EVENT_NEW_SECTION; |
| event.params.section.total_length = buffer1_len + buffer2_len; |
| event.params.section.actual_length = |
| event.params.section.total_length; |
| |
| dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| |
| if (dmxdevfilter->params.sec.flags & DMX_ONESHOT) |
| dmxdevfilter->state = DMXDEV_STATE_DONE; |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&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) |
| { |
| struct dmxdev_filter *dmxdevfilter = feed->priv; |
| struct dvb_ringbuffer *buffer; |
| struct dmxdev_events_queue *events; |
| struct dmx_filter_event event; |
| ssize_t free; |
| |
| if (!dmxdevfilter) { |
| pr_err("%s: null filter (feed->is_filtering=%d)\n", |
| __func__, feed->is_filtering); |
| return -EINVAL; |
| } |
| spin_lock(&dmxdevfilter->dev->lock); |
| |
| if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER || |
| dmxdevfilter->state != DMXDEV_STATE_GO || |
| dmxdevfilter->eos_state) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return 0; |
| } |
| |
| if (dmxdevfilter->params.pes.output != DMX_OUT_TS_TAP) { |
| buffer = &dmxdevfilter->buffer; |
| events = &dmxdevfilter->events; |
| } else { |
| buffer = &dmxdevfilter->dev->dvr_buffer; |
| events = &dmxdevfilter->dev->dvr_output_events; |
| } |
| |
| if (buffer->error) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return buffer->error; |
| } |
| |
| if (!events->current_event_data_size) |
| events->current_event_start_offset = buffer->pwrite; |
| |
| /* Verify output buffer has sufficient space, or report overflow */ |
| free = dvb_ringbuffer_free(buffer); |
| if (free < (buffer1_len + buffer2_len)) { |
| pr_debug("%s: buffer overflow error, pid=%u\n", |
| __func__, dmxdevfilter->params.pes.pid); |
| buffer->error = -EOVERFLOW; |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| |
| return -EOVERFLOW; |
| } |
| |
| if (buffer1_len + buffer2_len) { |
| dvb_dmxdev_buffer_write(buffer, buffer1, buffer1_len); |
| dvb_dmxdev_buffer_write(buffer, buffer2, buffer2_len); |
| |
| events->current_event_data_size += (buffer1_len + buffer2_len); |
| |
| if ((dmxdevfilter->params.pes.output == DMX_OUT_TS_TAP || |
| dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) |
| && events->current_event_data_size >= |
| dmxdevfilter->params.pes.rec_chunk_size) { |
| event.type = DMX_EVENT_NEW_REC_CHUNK; |
| event.params.recording_chunk.offset = |
| events->current_event_start_offset; |
| event.params.recording_chunk.size = |
| events->current_event_data_size; |
| |
| dvb_dmxdev_add_event(events, &event); |
| events->current_event_data_size = 0; |
| } |
| } |
| |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| static int dvb_dmxdev_section_event_cb(struct dmx_section_filter *filter, |
| struct dmx_data_ready *dmx_data_ready) |
| { |
| int res = 0; |
| struct dmxdev_filter *dmxdevfilter = filter->priv; |
| struct dmx_filter_event event; |
| ssize_t free; |
| |
| if (!dmxdevfilter) { |
| pr_err("%s: null filter. event type=%d (length=%d) will be discarded\n", |
| __func__, dmx_data_ready->status, |
| dmx_data_ready->data_length); |
| return -EINVAL; |
| } |
| |
| spin_lock(&dmxdevfilter->dev->lock); |
| |
| if (dmxdevfilter->buffer.error == -ETIMEDOUT || |
| dmxdevfilter->state != DMXDEV_STATE_GO || |
| dmxdevfilter->eos_state) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return 0; |
| } |
| |
| if (dmx_data_ready->data_length == 0) { |
| if (dmx_data_ready->status == DMX_CRC_ERROR) { |
| /* Section was dropped due to CRC error */ |
| event.type = DMX_EVENT_SECTION_CRC_ERROR; |
| dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| } else if (dmx_data_ready->status == DMX_OK_EOS) { |
| event.type = DMX_EVENT_EOS; |
| dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| } else if (dmx_data_ready->status == DMX_OK_MARKER) { |
| event.type = DMX_EVENT_MARKER; |
| event.params.marker.id = dmx_data_ready->marker.id; |
| dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| } else if (dmx_data_ready->status == DMX_OK_SCRAMBLING_STATUS) { |
| event.type = DMX_EVENT_SCRAMBLING_STATUS_CHANGE; |
| event.params.scrambling_status = |
| dmx_data_ready->scrambling_bits; |
| dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| } else if (dmx_data_ready->status == DMX_OVERRUN_ERROR) { |
| pr_debug("dmxdev: section filter overflow (pid=%u)\n", |
| dmxdevfilter->params.sec.pid); |
| /* Set buffer error to notify user overflow occurred */ |
| dmxdevfilter->buffer.error = -EOVERFLOW; |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| } else { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| } |
| return 0; |
| } |
| |
| event.type = DMX_EVENT_NEW_SECTION; |
| event.params.section.base_offset = dmxdevfilter->buffer.pwrite; |
| event.params.section.start_offset = dmxdevfilter->buffer.pwrite; |
| event.params.section.total_length = dmx_data_ready->data_length; |
| event.params.section.actual_length = dmx_data_ready->data_length; |
| |
| if (dmx_data_ready->status == DMX_MISSED_ERROR) |
| event.params.section.flags = DMX_FILTER_CC_ERROR; |
| else |
| event.params.section.flags = 0; |
| |
| free = dvb_ringbuffer_free(&dmxdevfilter->buffer); |
| if (free < dmx_data_ready->data_length) { |
| pr_err("%s: invalid data length: data_length=%d > free=%zd\n", |
| __func__, dmx_data_ready->data_length, free); |
| } else { |
| res = dvb_dmxdev_add_event(&dmxdevfilter->events, &event); |
| DVB_RINGBUFFER_PUSH(&dmxdevfilter->buffer, |
| dmx_data_ready->data_length); |
| } |
| |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| |
| return res; |
| } |
| |
| static int dvb_dmxdev_ts_event_cb(struct dmx_ts_feed *feed, |
| struct dmx_data_ready *dmx_data_ready) |
| { |
| struct dmxdev_filter *dmxdevfilter = feed->priv; |
| struct dvb_ringbuffer *buffer; |
| struct dmxdev_events_queue *events; |
| struct dmx_filter_event event; |
| ssize_t free; |
| |
| if (!dmxdevfilter) { |
| pr_err("%s: null filter (feed->is_filtering=%d) event type=%d (length=%d) will be discarded\n", |
| __func__, feed->is_filtering, |
| dmx_data_ready->status, |
| dmx_data_ready->data_length); |
| return -EINVAL; |
| } |
| |
| spin_lock(&dmxdevfilter->dev->lock); |
| |
| if (dmxdevfilter->state != DMXDEV_STATE_GO || |
| dmxdevfilter->eos_state) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| return 0; |
| } |
| |
| if (dmxdevfilter->params.pes.output != DMX_OUT_TS_TAP) { |
| buffer = &dmxdevfilter->buffer; |
| events = &dmxdevfilter->events; |
| } else { |
| buffer = &dmxdevfilter->dev->dvr_buffer; |
| events = &dmxdevfilter->dev->dvr_output_events; |
| } |
| |
| if (!buffer->error && dmx_data_ready->status == DMX_OVERRUN_ERROR) { |
| pr_debug("dmxdev: %s filter buffer overflow (pid=%u)\n", |
| dmxdevfilter->params.pes.output == DMX_OUT_DECODER ? |
| "decoder" : "", |
| dmxdevfilter->params.pes.pid); |
| /* Set buffer error to notify user overflow occurred */ |
| buffer->error = -EOVERFLOW; |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmx_data_ready->status == DMX_OK_EOS) { |
| /* Report partial recording chunk */ |
| if ((dmxdevfilter->params.pes.output == DMX_OUT_TS_TAP || |
| dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) |
| && events->current_event_data_size) { |
| event.type = DMX_EVENT_NEW_REC_CHUNK; |
| event.params.recording_chunk.offset = |
| events->current_event_start_offset; |
| event.params.recording_chunk.size = |
| events->current_event_data_size; |
| events->current_event_start_offset = |
| (events->current_event_start_offset + |
| events->current_event_data_size) % |
| buffer->size; |
| events->current_event_data_size = 0; |
| dvb_dmxdev_add_event(events, &event); |
| } |
| |
| dmxdevfilter->eos_state = 1; |
| pr_debug("dmxdev: DMX_OK_EOS - entering EOS state\n"); |
| event.type = DMX_EVENT_EOS; |
| dvb_dmxdev_add_event(events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmx_data_ready->status == DMX_OK_MARKER) { |
| pr_debug("dmxdev: DMX_OK_MARKER - id=%llu\n", |
| dmx_data_ready->marker.id); |
| event.type = DMX_EVENT_MARKER; |
| event.params.marker.id = dmx_data_ready->marker.id; |
| dvb_dmxdev_add_event(events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmx_data_ready->status == DMX_OK_PCR) { |
| pr_debug("dmxdev: event callback DMX_OK_PCR\n"); |
| event.type = DMX_EVENT_NEW_PCR; |
| event.params.pcr.pcr = dmx_data_ready->pcr.pcr; |
| event.params.pcr.stc = dmx_data_ready->pcr.stc; |
| if (dmx_data_ready->pcr.disc_indicator_set) |
| event.params.pcr.flags = |
| DMX_FILTER_DISCONTINUITY_INDICATOR; |
| else |
| event.params.pcr.flags = 0; |
| |
| dvb_dmxdev_add_event(events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmx_data_ready->status == DMX_OK_IDX) { |
| pr_debug("dmxdev: event callback DMX_OK_IDX\n"); |
| event.type = DMX_EVENT_NEW_INDEX_ENTRY; |
| event.params.index = dmx_data_ready->idx_event; |
| |
| dvb_dmxdev_add_event(events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmx_data_ready->status == DMX_OK_SCRAMBLING_STATUS) { |
| event.type = DMX_EVENT_SCRAMBLING_STATUS_CHANGE; |
| event.params.scrambling_status = |
| dmx_data_ready->scrambling_bits; |
| dvb_dmxdev_add_event(events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmx_data_ready->status == DMX_OK_DECODER_BUF) { |
| event.type = DMX_EVENT_NEW_ES_DATA; |
| event.params.es_data.buf_handle = dmx_data_ready->buf.handle; |
| event.params.es_data.cookie = dmx_data_ready->buf.cookie; |
| event.params.es_data.offset = dmx_data_ready->buf.offset; |
| event.params.es_data.data_len = dmx_data_ready->buf.len; |
| event.params.es_data.pts_valid = dmx_data_ready->buf.pts_exists; |
| event.params.es_data.pts = dmx_data_ready->buf.pts; |
| event.params.es_data.dts_valid = dmx_data_ready->buf.dts_exists; |
| event.params.es_data.dts = dmx_data_ready->buf.dts; |
| event.params.es_data.stc = dmx_data_ready->buf.stc; |
| event.params.es_data.transport_error_indicator_counter = |
| dmx_data_ready->buf.tei_counter; |
| event.params.es_data.continuity_error_counter = |
| dmx_data_ready->buf.cont_err_counter; |
| event.params.es_data.ts_packets_num = |
| dmx_data_ready->buf.ts_packets_num; |
| event.params.es_data.ts_dropped_bytes = |
| dmx_data_ready->buf.ts_dropped_bytes; |
| dvb_dmxdev_add_event(events, &event); |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) { |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| free = dvb_ringbuffer_free(buffer); |
| if (free < dmx_data_ready->data_length) { |
| pr_err("%s: invalid data length: data_length=%d > free=%zd\n", |
| __func__, dmx_data_ready->data_length, free); |
| |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&buffer->queue); |
| return 0; |
| } |
| |
| if (dmxdevfilter->params.pes.output == DMX_OUT_TAP) { |
| if (dmx_data_ready->status == DMX_OK && |
| !events->current_event_data_size) { |
| events->current_event_start_offset = buffer->pwrite; |
| } else if (dmx_data_ready->status == DMX_OK_PES_END) { |
| event.type = DMX_EVENT_NEW_PES; |
| |
| event.params.pes.base_offset = |
| events->current_event_start_offset; |
| event.params.pes.start_offset = |
| (events->current_event_start_offset + |
| dmx_data_ready->pes_end.start_gap) % |
| buffer->size; |
| |
| event.params.pes.actual_length = |
| dmx_data_ready->pes_end.actual_length; |
| event.params.pes.total_length = |
| events->current_event_data_size; |
| |
| event.params.pes.flags = 0; |
| if (dmx_data_ready->pes_end.disc_indicator_set) |
| event.params.pes.flags |= |
| DMX_FILTER_DISCONTINUITY_INDICATOR; |
| if (dmx_data_ready->pes_end.pes_length_mismatch) |
| event.params.pes.flags |= |
| DMX_FILTER_PES_LENGTH_ERROR; |
| |
| event.params.pes.stc = dmx_data_ready->pes_end.stc; |
| event.params.pes.transport_error_indicator_counter = |
| dmx_data_ready->pes_end.tei_counter; |
| event.params.pes.continuity_error_counter = |
| dmx_data_ready->pes_end.cont_err_counter; |
| event.params.pes.ts_packets_num = |
| dmx_data_ready->pes_end.ts_packets_num; |
| |
| /* Do not report zero length PES */ |
| if (event.params.pes.total_length) |
| dvb_dmxdev_add_event(events, &event); |
| |
| events->current_event_data_size = 0; |
| } |
| } else if (!events->current_event_data_size) { |
| events->current_event_start_offset = buffer->pwrite; |
| } |
| |
| events->current_event_data_size += dmx_data_ready->data_length; |
| DVB_RINGBUFFER_PUSH(buffer, dmx_data_ready->data_length); |
| |
| if ((dmxdevfilter->params.pes.output == DMX_OUT_TS_TAP) || |
| (dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP)) { |
| while (events->current_event_data_size >= |
| dmxdevfilter->params.pes.rec_chunk_size) { |
| event.type = DMX_EVENT_NEW_REC_CHUNK; |
| event.params.recording_chunk.offset = |
| events->current_event_start_offset; |
| event.params.recording_chunk.size = |
| dmxdevfilter->params.pes.rec_chunk_size; |
| events->current_event_data_size = |
| events->current_event_data_size - |
| dmxdevfilter->params.pes.rec_chunk_size; |
| events->current_event_start_offset = |
| (events->current_event_start_offset + |
| dmxdevfilter->params.pes.rec_chunk_size) % |
| buffer->size; |
| |
| dvb_dmxdev_add_event(events, &event); |
| } |
| } |
| spin_unlock(&dmxdevfilter->dev->lock); |
| wake_up_all(&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.feed->stop_filtering( |
| dmxdevfilter->feed.sec.feed); |
| break; |
| case DMXDEV_TYPE_PES: |
| list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) { |
| if (dmxdevfilter->params.pes.output == DMX_OUT_TS_TAP) { |
| dmxdevfilter->dev->dvr_feeds_count--; |
| if (!dmxdevfilter->dev->dvr_feeds_count) |
| dmxdevfilter->dev->dvr_feed = NULL; |
| } |
| 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.feed->start_filtering( |
| filter->feed.sec.feed); |
| 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.feed); |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_filter_stop(struct dmxdev_filter *dmxdevfilter) |
| { |
| struct dmxdev_feed *feed; |
| struct dmx_demux *demux; |
| struct ts_insertion_buffer *ts_buffer; |
| |
| if (dmxdevfilter->state < DMXDEV_STATE_GO) |
| return 0; |
| |
| switch (dmxdevfilter->type) { |
| case DMXDEV_TYPE_SEC: |
| if (!dmxdevfilter->feed.sec.feed) |
| break; |
| dvb_dmxdev_feed_stop(dmxdevfilter); |
| if (dmxdevfilter->filter.sec) |
| dmxdevfilter->feed.sec.feed-> |
| release_filter(dmxdevfilter->feed.sec.feed, |
| dmxdevfilter->filter.sec); |
| dvb_dmxdev_feed_restart(dmxdevfilter); |
| dmxdevfilter->feed.sec.feed = NULL; |
| break; |
| case DMXDEV_TYPE_PES: |
| dvb_dmxdev_feed_stop(dmxdevfilter); |
| demux = dmxdevfilter->dev->demux; |
| |
| if (!list_empty(&dmxdevfilter->insertion_buffers)) { |
| feed = list_first_entry(&dmxdevfilter->feed.ts, |
| struct dmxdev_feed, next); |
| |
| list_for_each_entry(ts_buffer, |
| &dmxdevfilter->insertion_buffers, next) |
| dvb_dmxdev_cancel_ts_insertion(ts_buffer); |
| if (feed->ts->ts_insertion_terminate) |
| feed->ts->ts_insertion_terminate(feed->ts); |
| } |
| |
| 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; |
| } |
| |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| dvb_dmxdev_flush_output(&dmxdevfilter->buffer, &dmxdevfilter->events); |
| dvb_ringbuffer_reset(&dmxdevfilter->buffer); |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| |
| 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; |
| dmx_output_t 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; |
| |
| if (filter->params.pes.output == DMX_OUT_TS_TAP) { |
| tsfeed->buffer.ringbuff = &dmxdev->dvr_buffer; |
| tsfeed->buffer.priv_handle = dmxdev->dvr_priv_buff_handle; |
| if (!dmxdev->dvr_feeds_count) |
| dmxdev->dvr_feed = filter; |
| dmxdev->dvr_feeds_count++; |
| } else if (filter->params.pes.output == DMX_OUT_DECODER) { |
| tsfeed->buffer.ringbuff = &filter->buffer; |
| tsfeed->decoder_buffers = &filter->decoder_buffers; |
| tsfeed->buffer.priv_handle = filter->priv_buff_handle; |
| } else { |
| tsfeed->buffer.ringbuff = &filter->buffer; |
| tsfeed->buffer.priv_handle = filter->priv_buff_handle; |
| } |
| |
| if (tsfeed->data_ready_cb) { |
| ret = tsfeed->data_ready_cb(tsfeed, dvb_dmxdev_ts_event_cb); |
| |
| if (ret < 0) { |
| dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); |
| return ret; |
| } |
| } |
| |
| ret = tsfeed->set(tsfeed, feed->pid, |
| ts_type, ts_pes, |
| filter->decoder_buffers.buffers_size, |
| timeout); |
| if (ret < 0) { |
| dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); |
| return ret; |
| } |
| |
| if (tsfeed->set_tsp_out_format) |
| tsfeed->set_tsp_out_format(tsfeed, filter->dmx_tsp_format); |
| |
| if (tsfeed->set_secure_mode) |
| tsfeed->set_secure_mode(tsfeed, &filter->sec_mode); |
| |
| if (tsfeed->set_cipher_ops) |
| tsfeed->set_cipher_ops(tsfeed, &feed->cipher_ops); |
| |
| if ((para->pes_type == DMX_PES_VIDEO0) || |
| (para->pes_type == DMX_PES_VIDEO1) || |
| (para->pes_type == DMX_PES_VIDEO2) || |
| (para->pes_type == DMX_PES_VIDEO3)) { |
| if (tsfeed->set_video_codec) { |
| ret = tsfeed->set_video_codec(tsfeed, |
| para->video_codec); |
| |
| if (ret < 0) { |
| dmxdev->demux->release_ts_feed(dmxdev->demux, |
| tsfeed); |
| return ret; |
| } |
| } |
| } |
| |
| if ((filter->params.pes.output == DMX_OUT_TS_TAP) || |
| (filter->params.pes.output == DMX_OUT_TSDEMUX_TAP)) |
| if (tsfeed->set_idx_params) { |
| ret = tsfeed->set_idx_params( |
| tsfeed, &feed->idx_params); |
| if (ret) { |
| 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_filter_external_buffer_only(struct dmxdev *dmxdev, |
| struct dmxdev_filter *filter) |
| { |
| struct dmx_caps caps; |
| int is_external_only; |
| int flags; |
| |
| /* |
| * For backward compatibility, default assumes that |
| * external only buffers are not supported. |
| */ |
| flags = 0; |
| if (dmxdev->demux->get_caps) { |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| |
| if (filter->type == DMXDEV_TYPE_SEC) |
| flags = caps.section.flags; |
| else if (filter->params.pes.output == DMX_OUT_DECODER) |
| /* For decoder filters dmxdev buffer is not required */ |
| flags = 0; |
| else if (filter->params.pes.output == DMX_OUT_TAP) |
| flags = caps.pes.flags; |
| else if (filter->dmx_tsp_format == DMX_TSP_FORMAT_188) |
| flags = caps.recording_188_tsp.flags; |
| else |
| flags = caps.recording_192_tsp.flags; |
| } |
| |
| if (!(flags & DMX_BUFFER_INTERNAL_SUPPORT) && |
| (flags & DMX_BUFFER_EXTERNAL_SUPPORT)) |
| is_external_only = 1; |
| else |
| is_external_only = 0; |
| |
| return is_external_only; |
| } |
| |
| static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) |
| { |
| struct dmxdev *dmxdev = filter->dev; |
| struct dmxdev_feed *feed; |
| void *mem; |
| int ret, i; |
| size_t tsp_size; |
| |
| if (filter->state < DMXDEV_STATE_SET) |
| return -EINVAL; |
| |
| if (filter->state >= DMXDEV_STATE_GO) |
| dvb_dmxdev_filter_stop(filter); |
| |
| if (!dvb_filter_verify_buffer_size(filter)) |
| return -EINVAL; |
| |
| if (!filter->buffer.data) { |
| /* |
| * dmxdev buffer in decoder filters is not really used |
| * to exchange data with applications. Decoder buffers |
| * can be set using DMX_SET_DECODER_BUFFER, which |
| * would not update the filter->buffer.data at all. |
| * Therefore we should not treat this filter as |
| * other regular filters and should not fail here |
| * even if user sets the buffer in deocder |
| * filter as external buffer. |
| */ |
| if (filter->type == DMXDEV_TYPE_PES && |
| (filter->params.pes.output == DMX_OUT_DECODER || |
| filter->params.pes.output == DMX_OUT_TS_TAP)) |
| filter->buffer_mode = DMX_BUFFER_MODE_INTERNAL; |
| |
| if (!(filter->type == DMXDEV_TYPE_PES && |
| filter->params.pes.output == DMX_OUT_TS_TAP) && |
| (filter->buffer_mode == DMX_BUFFER_MODE_EXTERNAL || |
| dvb_filter_external_buffer_only(dmxdev, filter))) |
| return -ENOMEM; |
| |
| mem = vmalloc_user(filter->buffer.size); |
| if (!mem) |
| return -ENOMEM; |
| spin_lock_irq(&filter->dev->lock); |
| filter->buffer.data = mem; |
| spin_unlock_irq(&filter->dev->lock); |
| } else if ((filter->buffer_mode == DMX_BUFFER_MODE_INTERNAL) && |
| dvb_filter_external_buffer_only(dmxdev, filter)) { |
| return -ENOMEM; |
| } |
| |
| filter->eos_state = 0; |
| |
| spin_lock_irq(&filter->dev->lock); |
| dvb_dmxdev_flush_output(&filter->buffer, &filter->events); |
| spin_unlock_irq(&filter->dev->lock); |
| |
| 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.feed; |
| |
| *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.feed; |
| 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; |
| } |
| |
| if ((*secfeed)->data_ready_cb) { |
| ret = (*secfeed)->data_ready_cb( |
| *secfeed, |
| dvb_dmxdev_section_event_cb); |
| |
| if (ret < 0) { |
| pr_err( |
| "DVB (%s): could not set event cb\n", |
| __func__); |
| dvb_dmxdev_feed_restart(filter); |
| return ret; |
| } |
| } |
| |
| ret = (*secfeed)->set(*secfeed, para->pid, 32768, |
| (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; |
| } |
| |
| if ((*secfeed)->set_secure_mode) |
| (*secfeed)->set_secure_mode(*secfeed, |
| &filter->sec_mode); |
| |
| if ((*secfeed)->set_cipher_ops) |
| (*secfeed)->set_cipher_ops(*secfeed, |
| &filter->feed.sec.cipher_ops); |
| } else { |
| dvb_dmxdev_feed_stop(filter); |
| } |
| |
| ret = (*secfeed)->allocate_filter(*secfeed, secfilter); |
| if (ret < 0) { |
| dvb_dmxdev_feed_restart(filter); |
| filter->feed.sec.feed->start_filtering(*secfeed); |
| pr_debug("could not get filter\n"); |
| return ret; |
| } |
| |
| (*secfilter)->priv = filter; |
| (*secfilter)->buffer.ringbuff = &filter->buffer; |
| (*secfilter)->buffer.priv_handle = filter->priv_buff_handle; |
| |
| 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; |
| filter->events.data_read_event_masked = |
| filter->events.event_mask.disable_mask & |
| DMX_EVENT_NEW_SECTION; |
| |
| ret = filter->feed.sec.feed->start_filtering( |
| filter->feed.sec.feed); |
| if (ret < 0) |
| return ret; |
| |
| dvb_dmxdev_filter_timer(filter); |
| break; |
| } |
| case DMXDEV_TYPE_PES: |
| if (filter->params.pes.rec_chunk_size < |
| DMX_REC_BUFF_CHUNK_MIN_SIZE) |
| filter->params.pes.rec_chunk_size = |
| DMX_REC_BUFF_CHUNK_MIN_SIZE; |
| |
| if (filter->params.pes.rec_chunk_size >= |
| filter->buffer.size) |
| filter->params.pes.rec_chunk_size = |
| filter->buffer.size >> 2; |
| |
| /* Align rec-chunk based on output format */ |
| if (filter->dmx_tsp_format == DMX_TSP_FORMAT_188) |
| tsp_size = 188; |
| else |
| tsp_size = 192; |
| |
| filter->params.pes.rec_chunk_size /= tsp_size; |
| filter->params.pes.rec_chunk_size *= tsp_size; |
| |
| if (filter->params.pes.output == DMX_OUT_TS_TAP) |
| dmxdev->dvr_output_events.data_read_event_masked = |
| dmxdev->dvr_output_events.event_mask.disable_mask & |
| DMX_EVENT_NEW_REC_CHUNK; |
| else if (filter->params.pes.output == DMX_OUT_TSDEMUX_TAP) |
| filter->events.data_read_event_masked = |
| filter->events.event_mask.disable_mask & |
| DMX_EVENT_NEW_REC_CHUNK; |
| else if (filter->params.pes.output == DMX_OUT_TAP) |
| filter->events.data_read_event_masked = |
| filter->events.event_mask.disable_mask & |
| DMX_EVENT_NEW_PES; |
| else |
| filter->events.data_read_event_masked = 1; |
| |
| ret = 0; |
| list_for_each_entry(feed, &filter->feed.ts, next) { |
| ret = dvb_dmxdev_start_feed(dmxdev, filter, feed); |
| if (ret) |
| break; |
| } |
| |
| if (!ret) |
| break; |
| |
| /* cleanup feeds that were started before the failure */ |
| list_for_each_entry(feed, &filter->feed.ts, next) { |
| if (!feed->ts) |
| continue; |
| feed->ts->stop_filtering(feed->ts); |
| dmxdev->demux->release_ts_feed(dmxdev->demux, feed->ts); |
| feed->ts = NULL; |
| |
| if (filter->params.pes.output == DMX_OUT_TS_TAP) { |
| filter->dev->dvr_feeds_count--; |
| if (!filter->dev->dvr_feeds_count) |
| filter->dev->dvr_feed = NULL; |
| } |
| } |
| return ret; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); |
| |
| if ((filter->type == DMXDEV_TYPE_PES) && |
| !list_empty(&filter->insertion_buffers)) { |
| struct ts_insertion_buffer *ts_buffer; |
| |
| feed = list_first_entry(&filter->feed.ts, |
| struct dmxdev_feed, next); |
| |
| ret = 0; |
| if (feed->ts->ts_insertion_init) |
| ret = feed->ts->ts_insertion_init(feed->ts); |
| if (!ret) { |
| list_for_each_entry(ts_buffer, |
| &filter->insertion_buffers, next) |
| dvb_dmxdev_queue_ts_insertion( |
| ts_buffer); |
| } else { |
| pr_err("%s: ts_insertion_init failed, err %d\n", |
| __func__, ret); |
| } |
| } |
| |
| 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; |
| |
| memset(&dmxdevfilter->decoder_buffers, |
| 0, |
| sizeof(dmxdevfilter->decoder_buffers)); |
| dmxdevfilter->decoder_buffers.buffers_size = |
| DMX_DEFAULT_DECODER_BUFFER_SIZE; |
| dmxdevfilter->buffer_mode = DMX_BUFFER_MODE_INTERNAL; |
| dmxdevfilter->priv_buff_handle = NULL; |
| dvb_ringbuffer_init(&dmxdevfilter->buffer, NULL, 8192); |
| dvb_dmxdev_flush_events(&dmxdevfilter->events); |
| dmxdevfilter->events.event_mask.disable_mask = DMX_EVENT_NEW_ES_DATA; |
| dmxdevfilter->events.event_mask.no_wakeup_mask = 0; |
| dmxdevfilter->events.event_mask.wakeup_threshold = 1; |
| |
| dmxdevfilter->type = DMXDEV_TYPE_NONE; |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); |
| init_timer(&dmxdevfilter->timer); |
| |
| dmxdevfilter->sec_mode.is_secured = 0; |
| |
| INIT_LIST_HEAD(&dmxdevfilter->insertion_buffers); |
| |
| dmxdevfilter->dmx_tsp_format = DMX_TSP_FORMAT_188; |
| dvbdev->users++; |
| |
| mutex_unlock(&dmxdev->mutex); |
| return 0; |
| } |
| |
| static int dvb_dmxdev_filter_free(struct dmxdev *dmxdev, |
| struct dmxdev_filter *dmxdevfilter) |
| { |
| struct ts_insertion_buffer *ts_buffer, *tmp; |
| |
| mutex_lock(&dmxdev->mutex); |
| mutex_lock(&dmxdevfilter->mutex); |
| |
| dvb_dmxdev_filter_stop(dmxdevfilter); |
| dvb_dmxdev_filter_reset(dmxdevfilter); |
| |
| list_for_each_entry_safe(ts_buffer, tmp, |
| &dmxdevfilter->insertion_buffers, next) { |
| list_del(&ts_buffer->next); |
| vfree(ts_buffer->buffer); |
| vfree(ts_buffer); |
| } |
| |
| if (dmxdevfilter->buffer.data) { |
| void *mem = dmxdevfilter->buffer.data; |
| |
| spin_lock_irq(&dmxdev->lock); |
| dmxdevfilter->buffer.data = NULL; |
| spin_unlock_irq(&dmxdev->lock); |
| if (dmxdevfilter->buffer_mode == DMX_BUFFER_MODE_INTERNAL) |
| vfree(mem); |
| } |
| |
| if ((dmxdevfilter->buffer_mode == DMX_BUFFER_MODE_EXTERNAL) && |
| dmxdevfilter->priv_buff_handle) { |
| dmxdev->demux->unmap_buffer(dmxdev->demux, |
| dmxdevfilter->priv_buff_handle); |
| dmxdevfilter->priv_buff_handle = NULL; |
| } |
| |
| dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_FREE); |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| mutex_unlock(&dmxdevfilter->mutex); |
| mutex_unlock(&dmxdev->mutex); |
| return 0; |
| } |
| |
| static inline void invert_mode(dmx_filter_t *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; |
| int ret = 0; |
| |
| 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; |
| feed->cipher_ops.operations_count = 0; |
| feed->idx_params.enable = 0; |
| |
| if (filter->state >= DMXDEV_STATE_GO) |
| ret = dvb_dmxdev_start_feed(dmxdev, filter, feed); |
| |
| if (!ret) |
| list_add(&feed->next, &filter->feed.ts); |
| else |
| kfree(feed); |
| |
| return ret; |
| } |
| |
| static int dvb_dmxdev_remove_pid(struct dmxdev *dmxdev, |
| struct dmxdev_filter *filter, u16 pid) |
| { |
| int feed_count; |
| struct dmxdev_feed *feed, *tmp; |
| |
| if ((filter->type != DMXDEV_TYPE_PES) || |
| (filter->state < DMXDEV_STATE_SET)) |
| return -EINVAL; |
| |
| feed_count = 0; |
| list_for_each_entry(tmp, &filter->feed.ts, next) |
| feed_count++; |
| |
| if (feed_count <= 1) |
| return -EINVAL; |
| |
| list_for_each_entry_safe(feed, tmp, &filter->feed.ts, next) { |
| if (feed->pid == pid) { |
| if (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) |
| { |
| pr_debug("function : %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); |
| dmxdevfilter->feed.sec.cipher_ops.operations_count = 0; |
| 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_set_secure_mode( |
| struct dmxdev *dmxdev, |
| struct dmxdev_filter *filter, |
| struct dmx_secure_mode *sec_mode) |
| { |
| if (!dmxdev || !filter || !sec_mode) |
| return -EINVAL; |
| |
| if (filter->state == DMXDEV_STATE_GO) { |
| pr_err("%s: invalid filter state\n", __func__); |
| return -EBUSY; |
| } |
| |
| pr_debug("%s: secure=%d\n", __func__, sec_mode->is_secured); |
| |
| filter->sec_mode = *sec_mode; |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_set_cipher(struct dmxdev *dmxdev, |
| struct dmxdev_filter *filter, |
| struct dmx_cipher_operations *cipher_ops) |
| { |
| struct dmxdev_feed *feed; |
| struct dmxdev_feed *ts_feed = NULL; |
| struct dmxdev_sec_feed *sec_feed = NULL; |
| struct dmx_caps caps; |
| |
| if (!dmxdev || !dmxdev->demux->get_caps) |
| return -EINVAL; |
| |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| |
| if (!filter || !cipher_ops || |
| (cipher_ops->operations_count > caps.num_cipher_ops) || |
| (cipher_ops->operations_count > |
| DMX_MAX_CIPHER_OPERATIONS_COUNT)) |
| return -EINVAL; |
| |
| pr_debug("%s: pid=%d, operations=%d\n", __func__, |
| cipher_ops->pid, cipher_ops->operations_count); |
| |
| if (filter->state < DMXDEV_STATE_SET || |
| filter->state > DMXDEV_STATE_GO) { |
| pr_err("%s: invalid filter state\n", __func__); |
| return -EPERM; |
| } |
| |
| if (!filter->sec_mode.is_secured && cipher_ops->operations_count) { |
| pr_err("%s: secure mode must be enabled to set cipher ops\n", |
| __func__); |
| return -EPERM; |
| } |
| |
| switch (filter->type) { |
| case DMXDEV_TYPE_PES: |
| list_for_each_entry(feed, &filter->feed.ts, next) { |
| if (feed->pid == cipher_ops->pid) { |
| ts_feed = feed; |
| ts_feed->cipher_ops = *cipher_ops; |
| if (filter->state == DMXDEV_STATE_GO && |
| ts_feed->ts->set_cipher_ops) |
| ts_feed->ts->set_cipher_ops( |
| ts_feed->ts, cipher_ops); |
| break; |
| } |
| } |
| break; |
| case DMXDEV_TYPE_SEC: |
| if (filter->params.sec.pid == cipher_ops->pid) { |
| sec_feed = &filter->feed.sec; |
| sec_feed->cipher_ops = *cipher_ops; |
| if (filter->state == DMXDEV_STATE_GO && |
| sec_feed->feed->set_cipher_ops) |
| sec_feed->feed->set_cipher_ops(sec_feed->feed, |
| cipher_ops); |
| } |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if (!ts_feed && !sec_feed) { |
| pr_err("%s: pid %d is undefined for this filter\n", |
| __func__, cipher_ops->pid); |
| return -EINVAL; |
| } |
| |
| 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)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 int dvb_dmxdev_set_decoder_buffer(struct dmxdev *dmxdev, |
| struct dmxdev_filter *filter, |
| struct dmx_decoder_buffers *buffs) |
| { |
| int i; |
| struct dmx_decoder_buffers *dec_buffs; |
| struct dmx_caps caps; |
| |
| if (!dmxdev || !filter || !buffs) |
| return -EINVAL; |
| |
| dec_buffs = &filter->decoder_buffers; |
| if (!dmxdev->demux->get_caps) |
| return -EINVAL; |
| |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| if (!dvb_dmxdev_verify_buffer_size(buffs->buffers_size, |
| caps.decoder.max_size, caps.decoder.size_alignment)) |
| return -EINVAL; |
| |
| if ((buffs->buffers_size == 0) || |
| (buffs->is_linear && |
| ((buffs->buffers_num <= 1) || |
| (buffs->buffers_num > DMX_MAX_DECODER_BUFFER_NUM)))) |
| return -EINVAL; |
| |
| if (buffs->buffers_num == 0) { |
| /* Internal mode - linear buffers not supported in this mode */ |
| if (!(caps.decoder.flags & DMX_BUFFER_INTERNAL_SUPPORT) || |
| buffs->is_linear) |
| return -EINVAL; |
| } else { |
| /* External buffer(s) mode */ |
| if ((!(caps.decoder.flags & DMX_BUFFER_LINEAR_GROUP_SUPPORT) && |
| buffs->buffers_num > 1) || |
| !(caps.decoder.flags & DMX_BUFFER_EXTERNAL_SUPPORT) || |
| buffs->buffers_num > caps.decoder.max_buffer_num) |
| return -EINVAL; |
| |
| dec_buffs->is_linear = buffs->is_linear; |
| dec_buffs->buffers_num = buffs->buffers_num; |
| dec_buffs->buffers_size = buffs->buffers_size; |
| for (i = 0; i < dec_buffs->buffers_num; i++) |
| dec_buffs->handles[i] = buffs->handles[i]; |
| } |
| |
| 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, &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, &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->eos_state && |
| dvb_ringbuffer_empty(&dmxdevfilter->buffer)) { |
| mutex_unlock(&dmxdevfilter->mutex); |
| return 0; |
| } |
| |
| if (dmxdevfilter->type == DMXDEV_TYPE_SEC) |
| ret = dvb_dmxdev_read_sec(dmxdevfilter, file, buf, count, ppos); |
| else |
| ret = dvb_dmxdev_buffer_read(dmxdevfilter, |
| &dmxdevfilter->buffer, |
| file->f_flags & O_NONBLOCK, |
| buf, count, ppos); |
| |
| if (ret > 0) { |
| dvb_dmxdev_notify_data_read(dmxdevfilter, ret); |
| spin_lock_irq(&dmxdevfilter->dev->lock); |
| dvb_dmxdev_update_events(&dmxdevfilter->events, ret); |
| spin_unlock_irq(&dmxdevfilter->dev->lock); |
| |
| /* |
| * in PULL mode, we might be stalling on |
| * event queue, so need to wake-up waiters |
| */ |
| if (dmxdevfilter->dev->playback_mode == DMX_PB_MODE_PULL) |
| wake_up_all(&dmxdevfilter->buffer.queue); |
| } else if (ret == -EOVERFLOW) { |
| dvb_dmxdev_auto_flush_buffer(dmxdevfilter, |
| &dmxdevfilter->buffer); |
| } |
| |
| 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_SET_BUFFER_MODE: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_set_buffer_mode(dmxdevfilter, |
| *(enum dmx_buffer_mode *)parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_BUFFER: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_set_buffer(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_GET_BUFFER_STATUS: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_get_buffer_status(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_RELEASE_DATA: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_release_data(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_CAPS: |
| if (!dmxdev->demux->get_caps) { |
| ret = -EINVAL; |
| break; |
| } |
| ret = dmxdev->demux->get_caps(dmxdev->demux, parg); |
| break; |
| |
| case DMX_SET_SOURCE: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_set_source(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_TS_PACKET_FORMAT: |
| if (!dmxdev->demux->set_tsp_format) { |
| ret = -EINVAL; |
| break; |
| } |
| |
| if (dmxdevfilter->state >= DMXDEV_STATE_GO) { |
| ret = -EBUSY; |
| break; |
| } |
| ret = dmxdev->demux->set_tsp_format( |
| dmxdev->demux, |
| *(enum dmx_tsp_format_t *)parg); |
| break; |
| |
| case DMX_SET_TS_OUT_FORMAT: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| |
| ret = dvb_dmxdev_set_tsp_out_format(dmxdevfilter, |
| *(enum dmx_tsp_format_t *)parg); |
| |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_DECODER_BUFFER_SIZE: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| |
| ret = dvb_dmxdev_set_decoder_buffer_size(dmxdevfilter, arg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_PLAYBACK_MODE: |
| ret = dvb_dmxdev_set_playback_mode( |
| dmxdevfilter, |
| *(enum dmx_playback_mode_t *)parg); |
| break; |
| |
| case DMX_GET_EVENT: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_get_event(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| 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; |
| |
| case DMX_SET_DECODER_BUFFER: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| ret = -ERESTARTSYS; |
| break; |
| } |
| ret = dvb_dmxdev_set_decoder_buffer(dmxdev, dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_SECURE_MODE: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| ret = -ERESTARTSYS; |
| break; |
| } |
| ret = dvb_dmxdev_set_secure_mode(dmxdev, dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_CIPHER: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| ret = -ERESTARTSYS; |
| break; |
| } |
| ret = dvb_dmxdev_set_cipher(dmxdev, dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_REUSE_DECODER_BUFFER: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_reuse_decoder_buf(dmxdevfilter, arg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_EVENTS_MASK: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_set_event_mask(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_GET_EVENTS_MASK: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_get_event_mask(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_INDEXING_PARAMS: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_set_indexing_params(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_SET_TS_INSERTION: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_set_ts_insertion(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_ABORT_TS_INSERTION: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_abort_ts_insertion(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_GET_SCRAMBLING_BITS: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_get_scrambling_bits(dmxdevfilter, parg); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| case DMX_FLUSH_BUFFER: |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| ret = dvb_dmxdev_flush_buffer(dmxdevfilter); |
| mutex_unlock(&dmxdevfilter->mutex); |
| break; |
| |
| default: |
| pr_err("%s: unknown ioctl code (0x%x)\n", |
| __func__, cmd); |
| ret = -ENOIOCTLCMD; |
| 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); |
| } |
| |
| #ifdef CONFIG_COMPAT |
| |
| struct dmx_set_ts_insertion32 { |
| __u32 identifier; |
| __u32 repetition_time; |
| compat_uptr_t ts_packets; |
| compat_size_t size; |
| }; |
| |
| static long dmx_set_ts_insertion32_wrapper(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| int ret; |
| struct dmx_set_ts_insertion32 dmx_ts_insert32; |
| struct dmx_set_ts_insertion dmx_ts_insert; |
| |
| ret = copy_from_user(&dmx_ts_insert32, (void __user *)arg, |
| sizeof(dmx_ts_insert32)); |
| if (ret) { |
| pr_err( |
| "%s: copy dmx_set_ts_insertion32 from user failed, ret=%d\n", |
| __func__, ret); |
| return -EFAULT; |
| } |
| |
| memset(&dmx_ts_insert, 0, sizeof(dmx_ts_insert)); |
| dmx_ts_insert.identifier = dmx_ts_insert32.identifier; |
| dmx_ts_insert.repetition_time = dmx_ts_insert32.repetition_time; |
| dmx_ts_insert.ts_packets = compat_ptr(dmx_ts_insert32.ts_packets); |
| dmx_ts_insert.size = dmx_ts_insert32.size; |
| |
| ret = dvb_demux_do_ioctl(file, DMX_SET_TS_INSERTION, &dmx_ts_insert); |
| |
| return ret; |
| } |
| |
| #define DMX_SET_TS_INSERTION32 _IOW('o', 70, struct dmx_set_ts_insertion32) |
| |
| /* |
| * compat ioctl is called whenever compatibility is required, i.e when a 32bit |
| * process calls an ioctl for a 64bit kernel. |
| */ |
| static long dvb_demux_compat_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| long ret = 0; |
| |
| switch (cmd) { |
| case DMX_SET_TS_INSERTION32: |
| ret = dmx_set_ts_insertion32_wrapper(file, cmd, arg); |
| break; |
| case DMX_SET_TS_INSERTION: |
| pr_err("%s: 64bit ioctl code (0x%lx) used by 32bit userspace\n", |
| __func__, DMX_SET_TS_INSERTION); |
| ret = -ENOIOCTLCMD; |
| break; |
| default: |
| /* use regular ioctl */ |
| ret = dvb_usercopy(file, cmd, arg, dvb_demux_do_ioctl); |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| static unsigned int dvb_demux_poll(struct file *file, poll_table *wait) |
| { |
| struct dmxdev_filter *dmxdevfilter = file->private_data; |
| unsigned int mask = 0; |
| |
| if (!dmxdevfilter) |
| return -EINVAL; |
| |
| poll_wait(file, &dmxdevfilter->buffer.queue, wait); |
| |
| if (dmxdevfilter->state != DMXDEV_STATE_GO && |
| dmxdevfilter->state != DMXDEV_STATE_DONE && |
| dmxdevfilter->state != DMXDEV_STATE_TIMEDOUT) |
| return 0; |
| |
| if (dmxdevfilter->buffer.error) { |
| mask |= (POLLIN | POLLRDNORM | POLLERR); |
| if (dmxdevfilter->buffer.error == -EOVERFLOW) |
| mask |= POLLPRI; |
| } |
| |
| if (!dvb_ringbuffer_empty(&dmxdevfilter->buffer)) |
| mask |= (POLLIN | POLLRDNORM); |
| |
| if (dmxdevfilter->events.wakeup_events_counter >= |
| dmxdevfilter->events.event_mask.wakeup_threshold) |
| mask |= POLLPRI; |
| |
| return mask; |
| } |
| |
| static int dvb_demux_mmap(struct file *filp, struct vm_area_struct *vma) |
| { |
| struct dmxdev_filter *dmxdevfilter = filp->private_data; |
| struct dmxdev *dmxdev = dmxdevfilter->dev; |
| int ret; |
| int vma_size; |
| int buffer_size; |
| |
| vma_size = vma->vm_end - vma->vm_start; |
| |
| if (vma->vm_flags & VM_WRITE) |
| return -EINVAL; |
| |
| if (mutex_lock_interruptible(&dmxdev->mutex)) |
| return -ERESTARTSYS; |
| |
| if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { |
| mutex_unlock(&dmxdev->mutex); |
| return -ERESTARTSYS; |
| } |
| |
| if ((!dmxdevfilter->buffer.data) || |
| (dmxdevfilter->buffer_mode == DMX_BUFFER_MODE_EXTERNAL)) { |
| mutex_unlock(&dmxdevfilter->mutex); |
| mutex_unlock(&dmxdev->mutex); |
| return -EINVAL; |
| } |
| |
| /* Make sure requested mapping is not larger than buffer size */ |
| buffer_size = dmxdevfilter->buffer.size + (PAGE_SIZE-1); |
| buffer_size = buffer_size & ~(PAGE_SIZE-1); |
| |
| if (vma_size != buffer_size) { |
| mutex_unlock(&dmxdevfilter->mutex); |
| mutex_unlock(&dmxdev->mutex); |
| return -EINVAL; |
| } |
| |
| ret = remap_vmalloc_range(vma, dmxdevfilter->buffer.data, 0); |
| if (ret) { |
| mutex_unlock(&dmxdevfilter->mutex); |
| mutex_unlock(&dmxdev->mutex); |
| return ret; |
| } |
| |
| vma->vm_flags |= VM_DONTDUMP; |
| vma->vm_flags |= VM_DONTEXPAND; |
| |
| mutex_unlock(&dmxdevfilter->mutex); |
| mutex_unlock(&dmxdev->mutex); |
| |
| return 0; |
| } |
| |
| 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) { |
| fops_put(file->f_op); |
| file->f_op = NULL; |
| 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, |
| .open = dvb_demux_open, |
| .release = dvb_demux_release, |
| .poll = dvb_demux_poll, |
| .llseek = default_llseek, |
| .mmap = dvb_demux_mmap, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = dvb_demux_compat_ioctl, |
| #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, file->f_flags, arg); |
| break; |
| |
| case DMX_SET_BUFFER_MODE: |
| ret = dvb_dvr_set_buffer_mode(dmxdev, file->f_flags, |
| *(enum dmx_buffer_mode *)parg); |
| break; |
| |
| case DMX_SET_BUFFER: |
| ret = dvb_dvr_set_buffer(dmxdev, file->f_flags, parg); |
| break; |
| |
| case DMX_GET_BUFFER_STATUS: |
| ret = dvb_dvr_get_buffer_status(dmxdev, file->f_flags, parg); |
| break; |
| |
| case DMX_RELEASE_DATA: |
| ret = dvb_dvr_release_data(dmxdev, file->f_flags, arg); |
| break; |
| |
| case DMX_FEED_DATA: |
| ret = dvb_dvr_feed_data(dmxdev, file->f_flags, arg); |
| break; |
| |
| case DMX_GET_EVENT: |
| ret = dvb_dvr_get_event(dmxdev, file->f_flags, parg); |
| break; |
| |
| case DMX_PUSH_OOB_COMMAND: |
| ret = dvb_dvr_push_oob_cmd(dmxdev, file->f_flags, parg); |
| break; |
| |
| case DMX_FLUSH_BUFFER: |
| ret = dvb_dvr_flush_buffer(dmxdev, file->f_flags); |
| break; |
| |
| default: |
| ret = -ENOIOCTLCMD; |
| 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); |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long dvb_dvr_compat_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| return dvb_usercopy(file, cmd, arg, dvb_dvr_do_ioctl); |
| } |
| #endif |
| |
| static unsigned int dvb_dvr_poll(struct file *file, poll_table *wait) |
| { |
| struct dvb_device *dvbdev = file->private_data; |
| struct dmxdev *dmxdev = dvbdev->priv; |
| unsigned int mask = 0; |
| |
| pr_debug("function : %s\n", __func__); |
| |
| if ((file->f_flags & O_ACCMODE) == O_RDONLY) { |
| poll_wait(file, &dmxdev->dvr_buffer.queue, wait); |
| |
| if (dmxdev->dvr_buffer.error) { |
| mask |= (POLLIN | POLLRDNORM | POLLERR); |
| if (dmxdev->dvr_buffer.error == -EOVERFLOW) |
| mask |= POLLPRI; |
| } |
| |
| if (!dvb_ringbuffer_empty(&dmxdev->dvr_buffer)) |
| mask |= (POLLIN | POLLRDNORM); |
| |
| if (dmxdev->dvr_output_events.wakeup_events_counter >= |
| dmxdev->dvr_output_events.event_mask.wakeup_threshold) |
| mask |= POLLPRI; |
| } else { |
| poll_wait(file, &dmxdev->dvr_input_buffer.queue, wait); |
| if (dmxdev->dvr_input_buffer.error) |
| mask |= (POLLOUT | POLLRDNORM | POLLPRI | POLLERR); |
| |
| if (dvb_ringbuffer_free(&dmxdev->dvr_input_buffer)) |
| mask |= (POLLOUT | POLLRDNORM | POLLPRI); |
| } |
| |
| return mask; |
| } |
| |
| static const struct file_operations dvb_dvr_fops = { |
| .owner = THIS_MODULE, |
| .read = dvb_dvr_read, |
| .write = dvb_dvr_write, |
| .mmap = dvb_dvr_mmap, |
| .unlocked_ioctl = dvb_dvr_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = dvb_dvr_compat_ioctl, |
| #endif |
| .open = dvb_dvr_open, |
| .release = dvb_dvr_release, |
| .poll = dvb_dvr_poll, |
| .llseek = default_llseek, |
| }; |
| |
| 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 |
| }; |
| |
| |
| /** |
| * debugfs service to print active filters information. |
| */ |
| static int dvb_dmxdev_dbgfs_print(struct seq_file *s, void *p) |
| { |
| int i; |
| struct dmxdev *dmxdev = s->private; |
| struct dmxdev_filter *filter; |
| int active_count = 0; |
| struct dmx_buffer_status buffer_status; |
| struct dmx_scrambling_bits scrambling_bits; |
| static const char * const pes_feeds[] = {"DEC", "PES", "DVR", "REC"}; |
| int ret; |
| |
| if (!dmxdev) |
| return 0; |
| |
| for (i = 0; i < dmxdev->filternum; i++) { |
| filter = &dmxdev->filter[i]; |
| if (filter->state >= DMXDEV_STATE_GO) { |
| active_count++; |
| |
| seq_printf(s, "filter_%02d - ", i); |
| |
| if (filter->type == DMXDEV_TYPE_SEC) { |
| seq_puts(s, "type: SEC, "); |
| seq_printf(s, "PID %04d ", |
| filter->params.sec.pid); |
| scrambling_bits.pid = filter->params.sec.pid; |
| } else { |
| seq_printf(s, "type: %s, ", |
| pes_feeds[filter->params.pes.output]); |
| seq_printf(s, "PID: %04d ", |
| filter->params.pes.pid); |
| scrambling_bits.pid = filter->params.pes.pid; |
| } |
| |
| dvb_dmxdev_get_scrambling_bits(filter, |
| &scrambling_bits); |
| |
| if (filter->type == DMXDEV_TYPE_PES && |
| filter->params.pes.output == DMX_OUT_TS_TAP) |
| ret = dvb_dvr_get_buffer_status(dmxdev, |
| O_RDONLY, &buffer_status); |
| else |
| ret = dvb_dmxdev_get_buffer_status(filter, |
| &buffer_status); |
| if (!ret) { |
| seq_printf(s, "size: %08d, ", |
| buffer_status.size); |
| seq_printf(s, "fullness: %08d, ", |
| buffer_status.fullness); |
| seq_printf(s, "error: %d, ", |
| buffer_status.error); |
| } |
| |
| seq_printf(s, "scramble: %d, ", |
| scrambling_bits.value); |
| seq_printf(s, "secured: %d\n", |
| filter->sec_mode.is_secured); |
| } |
| } |
| |
| if (!active_count) |
| seq_puts(s, "No active filters\n"); |
| |
| return 0; |
| } |
| |
| static int dvb_dmxdev_dbgfs_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, dvb_dmxdev_dbgfs_print, inode->i_private); |
| } |
| |
| static const struct file_operations dbgfs_filters_fops = { |
| .open = dvb_dmxdev_dbgfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| .owner = THIS_MODULE, |
| }; |
| |
| int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) |
| { |
| int i; |
| struct dmx_caps caps; |
| |
| if (dmxdev->demux->open(dmxdev->demux) < 0) |
| return -EUSERS; |
| |
| dmxdev->filter = vmalloc(dmxdev->filternum * sizeof(struct dmxdev_filter)); |
| if (!dmxdev->filter) |
| return -ENOMEM; |
| |
| dmxdev->playback_mode = DMX_PB_MODE_PUSH; |
| dmxdev->demux->dvr_input_protected = 0; |
| |
| mutex_init(&dmxdev->mutex); |
| spin_lock_init(&dmxdev->lock); |
| spin_lock_init(&dmxdev->dvr_in_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, 0); |
| dvb_register_device(dvb_adapter, &dmxdev->dvr_dvbdev, &dvbdev_dvr, |
| dmxdev, DVB_DEVICE_DVR, 0); |
| |
| dvb_ringbuffer_init(&dmxdev->dvr_buffer, NULL, 8192); |
| dvb_ringbuffer_init(&dmxdev->dvr_input_buffer, NULL, 8192); |
| |
| /* Disable auto buffer flushing if plugin does not allow it */ |
| if (dmxdev->demux->get_caps) { |
| dmxdev->demux->get_caps(dmxdev->demux, &caps); |
| if (!(caps.caps & DMX_CAP_AUTO_BUFFER_FLUSH)) |
| overflow_auto_flush = 0; |
| } |
| |
| if (dmxdev->demux->debugfs_demux_dir) |
| debugfs_create_file("filters", 0444, |
| dmxdev->demux->debugfs_demux_dir, dmxdev, |
| &dbgfs_filters_fops); |
| |
| 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); |