diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/dvb-core | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/dvb-core')
-rw-r--r-- | drivers/media/dvb-core/Kconfig | 80 | ||||
-rw-r--r-- | drivers/media/dvb-core/Makefile | 13 | ||||
-rw-r--r-- | drivers/media/dvb-core/dmxdev.c | 1477 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvb_ca_en50221.c | 1983 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvb_demux.c | 1318 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvb_frontend.c | 3087 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvb_math.c | 141 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvb_net.c | 1664 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvb_ringbuffer.c | 369 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvb_vb2.c | 449 | ||||
-rw-r--r-- | drivers/media/dvb-core/dvbdev.c | 1137 |
11 files changed, 11718 insertions, 0 deletions
diff --git a/drivers/media/dvb-core/Kconfig b/drivers/media/dvb-core/Kconfig new file mode 100644 index 000000000..8b3f2d53c --- /dev/null +++ b/drivers/media/dvb-core/Kconfig @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# DVB device configuration +# + +config DVB_MMAP + bool "Enable DVB memory-mapped API (EXPERIMENTAL)" + depends on DVB_CORE + depends on VIDEO_DEV=y || VIDEO_DEV=DVB_CORE + select VIDEOBUF2_VMALLOC + help + This option enables DVB experimental memory-mapped API, which + reduces the number of context switches to read DVB buffers, as + the buffers can use mmap() syscalls. + + Support for it is experimental. Use with care. If unsure, + say N. + +config DVB_NET + bool "DVB Network Support" + default (NET && INET) + depends on NET && INET && DVB_CORE + help + This option enables DVB Network Support which is a part of the DVB + standard. It is used, for example, by automatic firmware updates used + on Set-Top-Boxes. It can also be used to access the Internet via the + DVB card, if the network provider supports it. + + You may want to disable the network support on embedded devices. If + unsure say Y. + +config DVB_MAX_ADAPTERS + int "maximum number of DVB/ATSC adapters" + depends on DVB_CORE + default 16 + range 1 255 + help + Maximum number of DVB/ATSC adapters. Increasing this number + increases the memory consumption of the DVB subsystem even + if a much lower number of DVB/ATSC adapters is present. + Only values in the range 4-32 are tested. + + If you are unsure about this, use the default value 16 + +config DVB_DYNAMIC_MINORS + bool "Dynamic DVB minor allocation" + depends on DVB_CORE + default y + help + If you say Y here, the DVB subsystem will use dynamic minor + allocation for any device that uses the DVB major number. + This means that you can have more than 4 of a single type + of device (like demuxes and frontends) per adapter, but udev + will be required to manage the device nodes. + + If you are unsure about this, say N here. + +config DVB_DEMUX_SECTION_LOSS_LOG + bool "Enable DVB demux section packet loss log" + depends on DVB_CORE + help + Enable extra log messages meant to detect packet loss + inside the Kernel. + + Should not be enabled on normal cases, as logs can + be very verbose. + + If you are unsure about this, say N here. + +config DVB_ULE_DEBUG + bool "Enable DVB net ULE packet debug messages" + depends on DVB_CORE + help + Enable extra log messages meant to detect problems while + handling DVB network ULE packet loss inside the Kernel. + + Should not be enabled on normal cases, as logs can + be very verbose. + + If you are unsure about this, say N here. diff --git a/drivers/media/dvb-core/Makefile b/drivers/media/dvb-core/Makefile new file mode 100644 index 000000000..62b028ded --- /dev/null +++ b/drivers/media/dvb-core/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the kernel DVB device drivers. +# + +dvb-net-$(CONFIG_DVB_NET) := dvb_net.o +dvb-vb2-$(CONFIG_DVB_MMAP) := dvb_vb2.o + +dvb-core-objs := dvbdev.o dmxdev.o dvb_demux.o \ + dvb_ca_en50221.o dvb_frontend.o \ + $(dvb-net-y) dvb_ringbuffer.o $(dvb-vb2-y) dvb_math.o + +obj-$(CONFIG_DVB_CORE) += dvb-core.o diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c new file mode 100644 index 000000000..9ce5f010d --- /dev/null +++ b/drivers/media/dvb-core/dmxdev.c @@ -0,0 +1,1477 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * dmxdev.c - DVB demultiplexer device + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + */ + +#define pr_fmt(fmt) "dmxdev: " fmt + +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/ioctl.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <media/dmxdev.h> +#include <media/dvb_vb2.h> + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +#define dprintk(fmt, arg...) do { \ + if (debug) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##arg); \ +} while (0) + +static int dvb_dmxdev_buffer_write(struct dvb_ringbuffer *buf, + const u8 *src, size_t len) +{ + ssize_t free; + + if (!len) + return 0; + if (!buf->data) + return 0; + + free = dvb_ringbuffer_free(buf); + if (len > free) { + dprintk("buffer overflow\n"); + return -EOVERFLOW; + } + + return dvb_ringbuffer_write(buf, src, len); +} + +static ssize_t dvb_dmxdev_buffer_read(struct dvb_ringbuffer *src, + int non_blocking, char __user *buf, + size_t count, loff_t *ppos) +{ + size_t todo; + ssize_t avail; + ssize_t ret = 0; + + if (!src->data) + return 0; + + if (src->error) { + ret = src->error; + dvb_ringbuffer_flush(src); + return ret; + } + + for (todo = count; todo > 0; todo -= ret) { + if (non_blocking && dvb_ringbuffer_empty(src)) { + ret = -EWOULDBLOCK; + break; + } + + ret = wait_event_interruptible(src->queue, + !dvb_ringbuffer_empty(src) || + (src->error != 0)); + if (ret < 0) + break; + + if (src->error) { + ret = src->error; + dvb_ringbuffer_flush(src); + break; + } + + avail = dvb_ringbuffer_avail(src); + if (avail > todo) + avail = todo; + + ret = dvb_ringbuffer_read_user(src, buf, avail); + if (ret < 0) + break; + + buf += ret; + } + + return (count - todo) ? (count - todo) : ret; +} + +static struct dmx_frontend *get_fe(struct dmx_demux *demux, int type) +{ + struct list_head *head, *pos; + + head = demux->get_frontends(demux); + if (!head) + return NULL; + list_for_each(pos, head) + if (DMX_FE_ENTRY(pos)->source == type) + return DMX_FE_ENTRY(pos); + + return NULL; +} + +static int dvb_dvr_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + struct dmx_frontend *front; + bool need_ringbuffer = false; + + dprintk("%s\n", __func__); + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + + dmxdev->may_do_mmap = 0; + + /* + * The logic here is a little tricky due to the ifdef. + * + * The ringbuffer is used for both read and mmap. + * + * It is not needed, however, on two situations: + * - Write devices (access with O_WRONLY); + * - For duplex device nodes, opened with O_RDWR. + */ + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + need_ringbuffer = true; + else if ((file->f_flags & O_ACCMODE) == O_RDWR) { + if (!(dmxdev->capabilities & DMXDEV_CAP_DUPLEX)) { +#ifdef CONFIG_DVB_MMAP + dmxdev->may_do_mmap = 1; + need_ringbuffer = true; +#else + mutex_unlock(&dmxdev->mutex); + return -EOPNOTSUPP; +#endif + } + } + + if (need_ringbuffer) { + void *mem; + + if (!dvbdev->readers) { + mutex_unlock(&dmxdev->mutex); + return -EBUSY; + } + mem = vmalloc(DVR_BUFFER_SIZE); + if (!mem) { + mutex_unlock(&dmxdev->mutex); + return -ENOMEM; + } + dvb_ringbuffer_init(&dmxdev->dvr_buffer, mem, DVR_BUFFER_SIZE); + if (dmxdev->may_do_mmap) + dvb_vb2_init(&dmxdev->dvr_vb2_ctx, "dvr", + file->f_flags & O_NONBLOCK); + dvbdev->readers--; + } + + if ((file->f_flags & O_ACCMODE) == O_WRONLY) { + dmxdev->dvr_orig_fe = dmxdev->demux->frontend; + + if (!dmxdev->demux->write) { + mutex_unlock(&dmxdev->mutex); + return -EOPNOTSUPP; + } + + front = get_fe(dmxdev->demux, DMX_MEMORY_FE); + + if (!front) { + mutex_unlock(&dmxdev->mutex); + return -EINVAL; + } + dmxdev->demux->disconnect_frontend(dmxdev->demux); + dmxdev->demux->connect_frontend(dmxdev->demux, front); + } + dvbdev->users++; + mutex_unlock(&dmxdev->mutex); + return 0; +} + +static int dvb_dvr_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + + mutex_lock(&dmxdev->mutex); + + if ((file->f_flags & O_ACCMODE) == O_WRONLY) { + dmxdev->demux->disconnect_frontend(dmxdev->demux); + dmxdev->demux->connect_frontend(dmxdev->demux, + dmxdev->dvr_orig_fe); + } + + if (((file->f_flags & O_ACCMODE) == O_RDONLY) || + dmxdev->may_do_mmap) { + if (dmxdev->may_do_mmap) { + if (dvb_vb2_is_streaming(&dmxdev->dvr_vb2_ctx)) + dvb_vb2_stream_off(&dmxdev->dvr_vb2_ctx); + dvb_vb2_release(&dmxdev->dvr_vb2_ctx); + } + dvbdev->readers++; + if (dmxdev->dvr_buffer.data) { + void *mem = dmxdev->dvr_buffer.data; + /*memory barrier*/ + mb(); + spin_lock_irq(&dmxdev->lock); + dmxdev->dvr_buffer.data = NULL; + spin_unlock_irq(&dmxdev->lock); + vfree(mem); + } + } + /* TODO */ + dvbdev->users--; + if (dvbdev->users == 1 && dmxdev->exit == 1) { + mutex_unlock(&dmxdev->mutex); + wake_up(&dvbdev->wait_queue); + } else + mutex_unlock(&dmxdev->mutex); + + return 0; +} + +static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + int ret; + + if (!dmxdev->demux->write) + return -EOPNOTSUPP; + if ((file->f_flags & O_ACCMODE) != O_WRONLY) + return -EINVAL; + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + ret = dmxdev->demux->write(dmxdev->demux, buf, count); + mutex_unlock(&dmxdev->mutex); + return ret; +} + +static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + + if (dmxdev->exit) + return -ENODEV; + + return dvb_dmxdev_buffer_read(&dmxdev->dvr_buffer, + file->f_flags & O_NONBLOCK, + buf, count, ppos); +} + +static int dvb_dvr_set_buffer_size(struct dmxdev *dmxdev, + unsigned long size) +{ + struct dvb_ringbuffer *buf = &dmxdev->dvr_buffer; + void *newmem; + void *oldmem; + + dprintk("%s\n", __func__); + + if (buf->size == size) + return 0; + if (!size) + return -EINVAL; + + newmem = vmalloc(size); + if (!newmem) + return -ENOMEM; + + oldmem = buf->data; + + spin_lock_irq(&dmxdev->lock); + buf->data = newmem; + buf->size = size; + + /* reset and not flush in case the buffer shrinks */ + dvb_ringbuffer_reset(buf); + spin_unlock_irq(&dmxdev->lock); + + vfree(oldmem); + + return 0; +} + +static inline void dvb_dmxdev_filter_state_set(struct dmxdev_filter + *dmxdevfilter, int state) +{ + spin_lock_irq(&dmxdevfilter->dev->lock); + dmxdevfilter->state = state; + spin_unlock_irq(&dmxdevfilter->dev->lock); +} + +static int dvb_dmxdev_set_buffer_size(struct dmxdev_filter *dmxdevfilter, + unsigned long size) +{ + struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; + void *newmem; + void *oldmem; + + if (buf->size == size) + return 0; + if (!size) + return -EINVAL; + if (dmxdevfilter->state >= DMXDEV_STATE_GO) + return -EBUSY; + + newmem = vmalloc(size); + if (!newmem) + return -ENOMEM; + + oldmem = buf->data; + + spin_lock_irq(&dmxdevfilter->dev->lock); + buf->data = newmem; + buf->size = size; + + /* reset and not flush in case the buffer shrinks */ + dvb_ringbuffer_reset(buf); + spin_unlock_irq(&dmxdevfilter->dev->lock); + + vfree(oldmem); + + return 0; +} + +static void dvb_dmxdev_filter_timeout(struct timer_list *t) +{ + struct dmxdev_filter *dmxdevfilter = from_timer(dmxdevfilter, t, timer); + + dmxdevfilter->buffer.error = -ETIMEDOUT; + spin_lock_irq(&dmxdevfilter->dev->lock); + dmxdevfilter->state = DMXDEV_STATE_TIMEDOUT; + spin_unlock_irq(&dmxdevfilter->dev->lock); + wake_up(&dmxdevfilter->buffer.queue); +} + +static void dvb_dmxdev_filter_timer(struct dmxdev_filter *dmxdevfilter) +{ + struct dmx_sct_filter_params *para = &dmxdevfilter->params.sec; + + del_timer(&dmxdevfilter->timer); + if (para->timeout) { + dmxdevfilter->timer.expires = + jiffies + 1 + (HZ / 2 + HZ * para->timeout) / 1000; + add_timer(&dmxdevfilter->timer); + } +} + +static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_section_filter *filter, + u32 *buffer_flags) +{ + struct dmxdev_filter *dmxdevfilter = filter->priv; + int ret; + + if (!dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx) && + dmxdevfilter->buffer.error) { + wake_up(&dmxdevfilter->buffer.queue); + return 0; + } + spin_lock(&dmxdevfilter->dev->lock); + if (dmxdevfilter->state != DMXDEV_STATE_GO) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + del_timer(&dmxdevfilter->timer); + dprintk("section callback %*ph\n", 6, buffer1); + if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) { + ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx, + buffer1, buffer1_len, + buffer_flags); + if (ret == buffer1_len) + ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx, + buffer2, buffer2_len, + buffer_flags); + } else { + ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, + buffer1, buffer1_len); + if (ret == buffer1_len) { + ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, + buffer2, buffer2_len); + } + } + if (ret < 0) + dmxdevfilter->buffer.error = ret; + if (dmxdevfilter->params.sec.flags & DMX_ONESHOT) + dmxdevfilter->state = DMXDEV_STATE_DONE; + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&dmxdevfilter->buffer.queue); + return 0; +} + +static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_ts_feed *feed, + u32 *buffer_flags) +{ + struct dmxdev_filter *dmxdevfilter = feed->priv; + struct dvb_ringbuffer *buffer; +#ifdef CONFIG_DVB_MMAP + struct dvb_vb2_ctx *ctx; +#endif + int ret; + + spin_lock(&dmxdevfilter->dev->lock); + if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + + if (dmxdevfilter->params.pes.output == DMX_OUT_TAP || + dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) { + buffer = &dmxdevfilter->buffer; +#ifdef CONFIG_DVB_MMAP + ctx = &dmxdevfilter->vb2_ctx; +#endif + } else { + buffer = &dmxdevfilter->dev->dvr_buffer; +#ifdef CONFIG_DVB_MMAP + ctx = &dmxdevfilter->dev->dvr_vb2_ctx; +#endif + } + + if (dvb_vb2_is_streaming(ctx)) { + ret = dvb_vb2_fill_buffer(ctx, buffer1, buffer1_len, + buffer_flags); + if (ret == buffer1_len) + ret = dvb_vb2_fill_buffer(ctx, buffer2, buffer2_len, + buffer_flags); + } else { + if (buffer->error) { + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&buffer->queue); + return 0; + } + ret = dvb_dmxdev_buffer_write(buffer, buffer1, buffer1_len); + if (ret == buffer1_len) + ret = dvb_dmxdev_buffer_write(buffer, + buffer2, buffer2_len); + } + if (ret < 0) + buffer->error = ret; + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&buffer->queue); + return 0; +} + +/* stop feed but only mark the specified filter as stopped (state set) */ +static int dvb_dmxdev_feed_stop(struct dmxdev_filter *dmxdevfilter) +{ + struct dmxdev_feed *feed; + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + switch (dmxdevfilter->type) { + case DMXDEV_TYPE_SEC: + del_timer(&dmxdevfilter->timer); + dmxdevfilter->feed.sec->stop_filtering(dmxdevfilter->feed.sec); + break; + case DMXDEV_TYPE_PES: + list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) + feed->ts->stop_filtering(feed->ts); + break; + default: + return -EINVAL; + } + return 0; +} + +/* start feed associated with the specified filter */ +static int dvb_dmxdev_feed_start(struct dmxdev_filter *filter) +{ + struct dmxdev_feed *feed; + int ret; + + dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); + + switch (filter->type) { + case DMXDEV_TYPE_SEC: + return filter->feed.sec->start_filtering(filter->feed.sec); + case DMXDEV_TYPE_PES: + list_for_each_entry(feed, &filter->feed.ts, next) { + ret = feed->ts->start_filtering(feed->ts); + if (ret < 0) { + dvb_dmxdev_feed_stop(filter); + return ret; + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/* restart section feed if it has filters left associated with it, + otherwise release the feed */ +static int dvb_dmxdev_feed_restart(struct dmxdev_filter *filter) +{ + int i; + struct dmxdev *dmxdev = filter->dev; + u16 pid = filter->params.sec.pid; + + for (i = 0; i < dmxdev->filternum; i++) + if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && + dmxdev->filter[i].type == DMXDEV_TYPE_SEC && + dmxdev->filter[i].params.sec.pid == pid) { + dvb_dmxdev_feed_start(&dmxdev->filter[i]); + return 0; + } + + filter->dev->demux->release_section_feed(dmxdev->demux, + filter->feed.sec); + + return 0; +} + +static int dvb_dmxdev_filter_stop(struct dmxdev_filter *dmxdevfilter) +{ + struct dmxdev_feed *feed; + struct dmx_demux *demux; + + if (dmxdevfilter->state < DMXDEV_STATE_GO) + return 0; + + switch (dmxdevfilter->type) { + case DMXDEV_TYPE_SEC: + if (!dmxdevfilter->feed.sec) + break; + dvb_dmxdev_feed_stop(dmxdevfilter); + if (dmxdevfilter->filter.sec) + dmxdevfilter->feed.sec-> + release_filter(dmxdevfilter->feed.sec, + dmxdevfilter->filter.sec); + dvb_dmxdev_feed_restart(dmxdevfilter); + dmxdevfilter->feed.sec = NULL; + break; + case DMXDEV_TYPE_PES: + dvb_dmxdev_feed_stop(dmxdevfilter); + demux = dmxdevfilter->dev->demux; + list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) { + demux->release_ts_feed(demux, feed->ts); + feed->ts = NULL; + } + break; + default: + if (dmxdevfilter->state == DMXDEV_STATE_ALLOCATED) + return 0; + return -EINVAL; + } + + dvb_ringbuffer_flush(&dmxdevfilter->buffer); + return 0; +} + +static void dvb_dmxdev_delete_pids(struct dmxdev_filter *dmxdevfilter) +{ + struct dmxdev_feed *feed, *tmp; + + /* delete all PIDs */ + list_for_each_entry_safe(feed, tmp, &dmxdevfilter->feed.ts, next) { + list_del(&feed->next); + kfree(feed); + } + + BUG_ON(!list_empty(&dmxdevfilter->feed.ts)); +} + +static inline int dvb_dmxdev_filter_reset(struct dmxdev_filter *dmxdevfilter) +{ + if (dmxdevfilter->state < DMXDEV_STATE_SET) + return 0; + + if (dmxdevfilter->type == DMXDEV_TYPE_PES) + dvb_dmxdev_delete_pids(dmxdevfilter); + + dmxdevfilter->type = DMXDEV_TYPE_NONE; + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); + return 0; +} + +static int dvb_dmxdev_start_feed(struct dmxdev *dmxdev, + struct dmxdev_filter *filter, + struct dmxdev_feed *feed) +{ + ktime_t timeout = ktime_set(0, 0); + struct dmx_pes_filter_params *para = &filter->params.pes; + enum dmx_output otype; + int ret; + int ts_type; + enum dmx_ts_pes ts_pes; + struct dmx_ts_feed *tsfeed; + + feed->ts = NULL; + otype = para->output; + + ts_pes = para->pes_type; + + if (ts_pes < DMX_PES_OTHER) + ts_type = TS_DECODER; + else + ts_type = 0; + + if (otype == DMX_OUT_TS_TAP) + ts_type |= TS_PACKET; + else if (otype == DMX_OUT_TSDEMUX_TAP) + ts_type |= TS_PACKET | TS_DEMUX; + else if (otype == DMX_OUT_TAP) + ts_type |= TS_PACKET | TS_DEMUX | TS_PAYLOAD_ONLY; + + ret = dmxdev->demux->allocate_ts_feed(dmxdev->demux, &feed->ts, + dvb_dmxdev_ts_callback); + if (ret < 0) + return ret; + + tsfeed = feed->ts; + tsfeed->priv = filter; + + ret = tsfeed->set(tsfeed, feed->pid, ts_type, ts_pes, timeout); + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); + return ret; + } + + ret = tsfeed->start_filtering(tsfeed); + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); + return ret; + } + + return 0; +} + +static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) +{ + struct dmxdev *dmxdev = filter->dev; + struct dmxdev_feed *feed; + void *mem; + int ret, i; + + if (filter->state < DMXDEV_STATE_SET) + return -EINVAL; + + if (filter->state >= DMXDEV_STATE_GO) + dvb_dmxdev_filter_stop(filter); + + if (!filter->buffer.data) { + mem = vmalloc(filter->buffer.size); + if (!mem) + return -ENOMEM; + spin_lock_irq(&filter->dev->lock); + filter->buffer.data = mem; + spin_unlock_irq(&filter->dev->lock); + } + + dvb_ringbuffer_flush(&filter->buffer); + + switch (filter->type) { + case DMXDEV_TYPE_SEC: + { + struct dmx_sct_filter_params *para = &filter->params.sec; + struct dmx_section_filter **secfilter = &filter->filter.sec; + struct dmx_section_feed **secfeed = &filter->feed.sec; + + *secfilter = NULL; + *secfeed = NULL; + + + /* find active filter/feed with same PID */ + for (i = 0; i < dmxdev->filternum; i++) { + if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && + dmxdev->filter[i].type == DMXDEV_TYPE_SEC && + dmxdev->filter[i].params.sec.pid == para->pid) { + *secfeed = dmxdev->filter[i].feed.sec; + break; + } + } + + /* if no feed found, try to allocate new one */ + if (!*secfeed) { + ret = dmxdev->demux->allocate_section_feed(dmxdev->demux, + secfeed, + dvb_dmxdev_section_callback); + if (!*secfeed) { + pr_err("DVB (%s): could not alloc feed\n", + __func__); + return ret; + } + + ret = (*secfeed)->set(*secfeed, para->pid, + (para->flags & DMX_CHECK_CRC) ? 1 : 0); + if (ret < 0) { + pr_err("DVB (%s): could not set feed\n", + __func__); + dvb_dmxdev_feed_restart(filter); + return ret; + } + } else { + dvb_dmxdev_feed_stop(filter); + } + + ret = (*secfeed)->allocate_filter(*secfeed, secfilter); + if (ret < 0) { + dvb_dmxdev_feed_restart(filter); + filter->feed.sec->start_filtering(*secfeed); + dprintk("could not get filter\n"); + return ret; + } + + (*secfilter)->priv = filter; + + memcpy(&((*secfilter)->filter_value[3]), + &(para->filter.filter[1]), DMX_FILTER_SIZE - 1); + memcpy(&(*secfilter)->filter_mask[3], + ¶->filter.mask[1], DMX_FILTER_SIZE - 1); + memcpy(&(*secfilter)->filter_mode[3], + ¶->filter.mode[1], DMX_FILTER_SIZE - 1); + + (*secfilter)->filter_value[0] = para->filter.filter[0]; + (*secfilter)->filter_mask[0] = para->filter.mask[0]; + (*secfilter)->filter_mode[0] = para->filter.mode[0]; + (*secfilter)->filter_mask[1] = 0; + (*secfilter)->filter_mask[2] = 0; + + filter->todo = 0; + + ret = filter->feed.sec->start_filtering(filter->feed.sec); + if (ret < 0) + return ret; + + dvb_dmxdev_filter_timer(filter); + break; + } + case DMXDEV_TYPE_PES: + list_for_each_entry(feed, &filter->feed.ts, next) { + ret = dvb_dmxdev_start_feed(dmxdev, filter, feed); + if (ret < 0) { + dvb_dmxdev_filter_stop(filter); + return ret; + } + } + break; + default: + return -EINVAL; + } + + dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); + return 0; +} + +static int dvb_demux_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + int i; + struct dmxdev_filter *dmxdevfilter; + + if (!dmxdev->filter) + return -EINVAL; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + + for (i = 0; i < dmxdev->filternum; i++) + if (dmxdev->filter[i].state == DMXDEV_STATE_FREE) + break; + + if (i == dmxdev->filternum) { + mutex_unlock(&dmxdev->mutex); + return -EMFILE; + } + + dmxdevfilter = &dmxdev->filter[i]; + mutex_init(&dmxdevfilter->mutex); + file->private_data = dmxdevfilter; + +#ifdef CONFIG_DVB_MMAP + dmxdev->may_do_mmap = 1; +#else + dmxdev->may_do_mmap = 0; +#endif + + dvb_ringbuffer_init(&dmxdevfilter->buffer, NULL, 8192); + dvb_vb2_init(&dmxdevfilter->vb2_ctx, "demux_filter", + file->f_flags & O_NONBLOCK); + dmxdevfilter->type = DMXDEV_TYPE_NONE; + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); + timer_setup(&dmxdevfilter->timer, dvb_dmxdev_filter_timeout, 0); + + dvbdev->users++; + + mutex_unlock(&dmxdev->mutex); + return 0; +} + +static int dvb_dmxdev_filter_free(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter) +{ + mutex_lock(&dmxdev->mutex); + mutex_lock(&dmxdevfilter->mutex); + if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) + dvb_vb2_stream_off(&dmxdevfilter->vb2_ctx); + dvb_vb2_release(&dmxdevfilter->vb2_ctx); + + + dvb_dmxdev_filter_stop(dmxdevfilter); + dvb_dmxdev_filter_reset(dmxdevfilter); + + if (dmxdevfilter->buffer.data) { + void *mem = dmxdevfilter->buffer.data; + + spin_lock_irq(&dmxdev->lock); + dmxdevfilter->buffer.data = NULL; + spin_unlock_irq(&dmxdev->lock); + vfree(mem); + } + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_FREE); + wake_up(&dmxdevfilter->buffer.queue); + mutex_unlock(&dmxdevfilter->mutex); + mutex_unlock(&dmxdev->mutex); + return 0; +} + +static inline void invert_mode(struct dmx_filter *filter) +{ + int i; + + for (i = 0; i < DMX_FILTER_SIZE; i++) + filter->mode[i] ^= 0xff; +} + +static int dvb_dmxdev_add_pid(struct dmxdev *dmxdev, + struct dmxdev_filter *filter, u16 pid) +{ + struct dmxdev_feed *feed; + + if ((filter->type != DMXDEV_TYPE_PES) || + (filter->state < DMXDEV_STATE_SET)) + return -EINVAL; + + /* only TS packet filters may have multiple PIDs */ + if ((filter->params.pes.output != DMX_OUT_TSDEMUX_TAP) && + (!list_empty(&filter->feed.ts))) + return -EINVAL; + + feed = kzalloc(sizeof(struct dmxdev_feed), GFP_KERNEL); + if (feed == NULL) + return -ENOMEM; + + feed->pid = pid; + list_add(&feed->next, &filter->feed.ts); + + if (filter->state >= DMXDEV_STATE_GO) + return dvb_dmxdev_start_feed(dmxdev, filter, feed); + + return 0; +} + +static int dvb_dmxdev_remove_pid(struct dmxdev *dmxdev, + struct dmxdev_filter *filter, u16 pid) +{ + struct dmxdev_feed *feed, *tmp; + + if ((filter->type != DMXDEV_TYPE_PES) || + (filter->state < DMXDEV_STATE_SET)) + return -EINVAL; + + list_for_each_entry_safe(feed, tmp, &filter->feed.ts, next) { + if ((feed->pid == pid) && (feed->ts != NULL)) { + feed->ts->stop_filtering(feed->ts); + filter->dev->demux->release_ts_feed(filter->dev->demux, + feed->ts); + list_del(&feed->next); + kfree(feed); + } + } + + return 0; +} + +static int dvb_dmxdev_filter_set(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter, + struct dmx_sct_filter_params *params) +{ + dprintk("%s: PID=0x%04x, flags=%02x, timeout=%d\n", + __func__, params->pid, params->flags, params->timeout); + + dvb_dmxdev_filter_stop(dmxdevfilter); + + dmxdevfilter->type = DMXDEV_TYPE_SEC; + memcpy(&dmxdevfilter->params.sec, + params, sizeof(struct dmx_sct_filter_params)); + invert_mode(&dmxdevfilter->params.sec.filter); + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + if (params->flags & DMX_IMMEDIATE_START) + return dvb_dmxdev_filter_start(dmxdevfilter); + + return 0; +} + +static int dvb_dmxdev_pes_filter_set(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter, + struct dmx_pes_filter_params *params) +{ + int ret; + + dvb_dmxdev_filter_stop(dmxdevfilter); + dvb_dmxdev_filter_reset(dmxdevfilter); + + if ((unsigned int)params->pes_type > DMX_PES_OTHER) + return -EINVAL; + + dmxdevfilter->type = DMXDEV_TYPE_PES; + memcpy(&dmxdevfilter->params, params, + sizeof(struct dmx_pes_filter_params)); + INIT_LIST_HEAD(&dmxdevfilter->feed.ts); + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, + dmxdevfilter->params.pes.pid); + if (ret < 0) + return ret; + + if (params->flags & DMX_IMMEDIATE_START) + return dvb_dmxdev_filter_start(dmxdevfilter); + + return 0; +} + +static ssize_t dvb_dmxdev_read_sec(struct dmxdev_filter *dfil, + struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int result, hcount; + int done = 0; + + if (dfil->todo <= 0) { + hcount = 3 + dfil->todo; + if (hcount > count) + hcount = count; + result = dvb_dmxdev_buffer_read(&dfil->buffer, + file->f_flags & O_NONBLOCK, + buf, hcount, ppos); + if (result < 0) { + dfil->todo = 0; + return result; + } + if (copy_from_user(dfil->secheader - dfil->todo, buf, result)) + return -EFAULT; + buf += result; + done = result; + count -= result; + dfil->todo -= result; + if (dfil->todo > -3) + return done; + dfil->todo = ((dfil->secheader[1] << 8) | dfil->secheader[2]) & 0xfff; + if (!count) + return done; + } + if (count > dfil->todo) + count = dfil->todo; + result = dvb_dmxdev_buffer_read(&dfil->buffer, + file->f_flags & O_NONBLOCK, + buf, count, ppos); + if (result < 0) + return result; + dfil->todo -= result; + return (result + done); +} + +static ssize_t +dvb_demux_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + int ret; + + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) + return -ERESTARTSYS; + + if (dmxdevfilter->type == DMXDEV_TYPE_SEC) + ret = dvb_dmxdev_read_sec(dmxdevfilter, file, buf, count, ppos); + else + ret = dvb_dmxdev_buffer_read(&dmxdevfilter->buffer, + file->f_flags & O_NONBLOCK, + buf, count, ppos); + + mutex_unlock(&dmxdevfilter->mutex); + return ret; +} + +static int dvb_demux_do_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + struct dmxdev *dmxdev = dmxdevfilter->dev; + unsigned long arg = (unsigned long)parg; + int ret = 0; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case DMX_START: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + if (dmxdevfilter->state < DMXDEV_STATE_SET) + ret = -EINVAL; + else + ret = dvb_dmxdev_filter_start(dmxdevfilter); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_STOP: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_filter_stop(dmxdevfilter); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_FILTER: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_filter_set(dmxdev, dmxdevfilter, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_PES_FILTER: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_pes_filter_set(dmxdev, dmxdevfilter, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_BUFFER_SIZE: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_set_buffer_size(dmxdevfilter, arg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_GET_PES_PIDS: + if (!dmxdev->demux->get_pes_pids) { + ret = -EINVAL; + break; + } + dmxdev->demux->get_pes_pids(dmxdev->demux, parg); + break; + + case DMX_GET_STC: + if (!dmxdev->demux->get_stc) { + ret = -EINVAL; + break; + } + ret = dmxdev->demux->get_stc(dmxdev->demux, + ((struct dmx_stc *)parg)->num, + &((struct dmx_stc *)parg)->stc, + &((struct dmx_stc *)parg)->base); + break; + + case DMX_ADD_PID: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + ret = -ERESTARTSYS; + break; + } + ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, *(u16 *)parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_REMOVE_PID: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + ret = -ERESTARTSYS; + break; + } + ret = dvb_dmxdev_remove_pid(dmxdev, dmxdevfilter, *(u16 *)parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + +#ifdef CONFIG_DVB_MMAP + case DMX_REQBUFS: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_vb2_reqbufs(&dmxdevfilter->vb2_ctx, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_QUERYBUF: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_vb2_querybuf(&dmxdevfilter->vb2_ctx, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_EXPBUF: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_vb2_expbuf(&dmxdevfilter->vb2_ctx, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_QBUF: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_vb2_qbuf(&dmxdevfilter->vb2_ctx, parg); + if (ret == 0 && !dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) + ret = dvb_vb2_stream_on(&dmxdevfilter->vb2_ctx); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_DQBUF: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_vb2_dqbuf(&dmxdevfilter->vb2_ctx, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; +#endif + default: + ret = -ENOTTY; + break; + } + mutex_unlock(&dmxdev->mutex); + return ret; +} + +static long dvb_demux_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return dvb_usercopy(file, cmd, arg, dvb_demux_do_ioctl); +} + +static __poll_t dvb_demux_poll(struct file *file, poll_table *wait) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + __poll_t mask = 0; + + poll_wait(file, &dmxdevfilter->buffer.queue, wait); + + if ((!dmxdevfilter) || dmxdevfilter->dev->exit) + return EPOLLERR; + if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) + return dvb_vb2_poll(&dmxdevfilter->vb2_ctx, file, wait); + + if (dmxdevfilter->state != DMXDEV_STATE_GO && + dmxdevfilter->state != DMXDEV_STATE_DONE && + dmxdevfilter->state != DMXDEV_STATE_TIMEDOUT) + return 0; + + if (dmxdevfilter->buffer.error) + mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI | EPOLLERR); + + if (!dvb_ringbuffer_empty(&dmxdevfilter->buffer)) + mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI); + + return mask; +} + +#ifdef CONFIG_DVB_MMAP +static int dvb_demux_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + struct dmxdev *dmxdev = dmxdevfilter->dev; + int ret; + + if (!dmxdev->may_do_mmap) + return -ENOTTY; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_vb2_mmap(&dmxdevfilter->vb2_ctx, vma); + + mutex_unlock(&dmxdevfilter->mutex); + mutex_unlock(&dmxdev->mutex); + + return ret; +} +#endif + +static int dvb_demux_release(struct inode *inode, struct file *file) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + struct dmxdev *dmxdev = dmxdevfilter->dev; + int ret; + + ret = dvb_dmxdev_filter_free(dmxdev, dmxdevfilter); + + mutex_lock(&dmxdev->mutex); + dmxdev->dvbdev->users--; + if (dmxdev->dvbdev->users == 1 && dmxdev->exit == 1) { + mutex_unlock(&dmxdev->mutex); + wake_up(&dmxdev->dvbdev->wait_queue); + } else + mutex_unlock(&dmxdev->mutex); + + return ret; +} + +static const struct file_operations dvb_demux_fops = { + .owner = THIS_MODULE, + .read = dvb_demux_read, + .unlocked_ioctl = dvb_demux_ioctl, + .compat_ioctl = dvb_demux_ioctl, + .open = dvb_demux_open, + .release = dvb_demux_release, + .poll = dvb_demux_poll, + .llseek = default_llseek, +#ifdef CONFIG_DVB_MMAP + .mmap = dvb_demux_mmap, +#endif +}; + +static const struct dvb_device dvbdev_demux = { + .priv = NULL, + .users = 1, + .writers = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-demux", +#endif + .fops = &dvb_demux_fops +}; + +static int dvb_dvr_do_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + unsigned long arg = (unsigned long)parg; + int ret; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case DMX_SET_BUFFER_SIZE: + ret = dvb_dvr_set_buffer_size(dmxdev, arg); + break; + +#ifdef CONFIG_DVB_MMAP + case DMX_REQBUFS: + ret = dvb_vb2_reqbufs(&dmxdev->dvr_vb2_ctx, parg); + break; + + case DMX_QUERYBUF: + ret = dvb_vb2_querybuf(&dmxdev->dvr_vb2_ctx, parg); + break; + + case DMX_EXPBUF: + ret = dvb_vb2_expbuf(&dmxdev->dvr_vb2_ctx, parg); + break; + + case DMX_QBUF: + ret = dvb_vb2_qbuf(&dmxdev->dvr_vb2_ctx, parg); + if (ret == 0 && !dvb_vb2_is_streaming(&dmxdev->dvr_vb2_ctx)) + ret = dvb_vb2_stream_on(&dmxdev->dvr_vb2_ctx); + break; + + case DMX_DQBUF: + ret = dvb_vb2_dqbuf(&dmxdev->dvr_vb2_ctx, parg); + break; +#endif + default: + ret = -ENOTTY; + break; + } + mutex_unlock(&dmxdev->mutex); + return ret; +} + +static long dvb_dvr_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(file, cmd, arg, dvb_dvr_do_ioctl); +} + +static __poll_t dvb_dvr_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + __poll_t mask = 0; + + dprintk("%s\n", __func__); + + poll_wait(file, &dmxdev->dvr_buffer.queue, wait); + + if (dmxdev->exit) + return EPOLLERR; + if (dvb_vb2_is_streaming(&dmxdev->dvr_vb2_ctx)) + return dvb_vb2_poll(&dmxdev->dvr_vb2_ctx, file, wait); + + if (((file->f_flags & O_ACCMODE) == O_RDONLY) || + dmxdev->may_do_mmap) { + if (dmxdev->dvr_buffer.error) + mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI | EPOLLERR); + + if (!dvb_ringbuffer_empty(&dmxdev->dvr_buffer)) + mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI); + } else + mask |= (EPOLLOUT | EPOLLWRNORM | EPOLLPRI); + + return mask; +} + +#ifdef CONFIG_DVB_MMAP +static int dvb_dvr_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + int ret; + + if (!dmxdev->may_do_mmap) + return -ENOTTY; + + if (dmxdev->exit) + return -ENODEV; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + ret = dvb_vb2_mmap(&dmxdev->dvr_vb2_ctx, vma); + mutex_unlock(&dmxdev->mutex); + return ret; +} +#endif + +static const struct file_operations dvb_dvr_fops = { + .owner = THIS_MODULE, + .read = dvb_dvr_read, + .write = dvb_dvr_write, + .unlocked_ioctl = dvb_dvr_ioctl, + .open = dvb_dvr_open, + .release = dvb_dvr_release, + .poll = dvb_dvr_poll, + .llseek = default_llseek, +#ifdef CONFIG_DVB_MMAP + .mmap = dvb_dvr_mmap, +#endif +}; + +static const struct dvb_device dvbdev_dvr = { + .priv = NULL, + .readers = 1, + .users = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-dvr", +#endif + .fops = &dvb_dvr_fops +}; +int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) +{ + int i, ret; + + if (dmxdev->demux->open(dmxdev->demux) < 0) + return -EUSERS; + + dmxdev->filter = vmalloc(array_size(sizeof(struct dmxdev_filter), + dmxdev->filternum)); + if (!dmxdev->filter) + return -ENOMEM; + + mutex_init(&dmxdev->mutex); + spin_lock_init(&dmxdev->lock); + for (i = 0; i < dmxdev->filternum; i++) { + dmxdev->filter[i].dev = dmxdev; + dmxdev->filter[i].buffer.data = NULL; + dvb_dmxdev_filter_state_set(&dmxdev->filter[i], + DMXDEV_STATE_FREE); + } + + ret = dvb_register_device(dvb_adapter, &dmxdev->dvbdev, &dvbdev_demux, dmxdev, + DVB_DEVICE_DEMUX, dmxdev->filternum); + if (ret < 0) + goto err_register_dvbdev; + + ret = dvb_register_device(dvb_adapter, &dmxdev->dvr_dvbdev, &dvbdev_dvr, + dmxdev, DVB_DEVICE_DVR, dmxdev->filternum); + if (ret < 0) + goto err_register_dvr_dvbdev; + + dvb_ringbuffer_init(&dmxdev->dvr_buffer, NULL, 8192); + + return 0; + +err_register_dvr_dvbdev: + dvb_unregister_device(dmxdev->dvbdev); +err_register_dvbdev: + vfree(dmxdev->filter); + dmxdev->filter = NULL; + return ret; +} + +EXPORT_SYMBOL(dvb_dmxdev_init); + +void dvb_dmxdev_release(struct dmxdev *dmxdev) +{ + mutex_lock(&dmxdev->mutex); + dmxdev->exit = 1; + mutex_unlock(&dmxdev->mutex); + + 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); diff --git a/drivers/media/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c new file mode 100644 index 000000000..baf64540d --- /dev/null +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -0,0 +1,1983 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * dvb_ca.c: generic DVB functions for EN50221 CAM interfaces + * + * Copyright (C) 2004 Andrew de Quincey + * + * Parts of this file were based on sources as follows: + * + * Copyright (C) 2003 Ralph Metzler <rjkm@metzlerbros.de> + * + * based on code: + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + */ + +#define pr_fmt(fmt) "dvb_ca_en50221: " fmt + +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/nospec.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/sched/signal.h> +#include <linux/kthread.h> + +#include <media/dvb_ca_en50221.h> +#include <media/dvb_ringbuffer.h> + +static int dvb_ca_en50221_debug; + +module_param_named(cam_debug, dvb_ca_en50221_debug, int, 0644); +MODULE_PARM_DESC(cam_debug, "enable verbose debug messages"); + +#define dprintk(fmt, arg...) do { \ + if (dvb_ca_en50221_debug) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), __func__, ##arg);\ +} while (0) + +#define INIT_TIMEOUT_SECS 10 + +#define HOST_LINK_BUF_SIZE 0x200 + +#define RX_BUFFER_SIZE 65535 + +#define MAX_RX_PACKETS_PER_ITERATION 10 + +#define CTRLIF_DATA 0 +#define CTRLIF_COMMAND 1 +#define CTRLIF_STATUS 1 +#define CTRLIF_SIZE_LOW 2 +#define CTRLIF_SIZE_HIGH 3 + +#define CMDREG_HC 1 /* Host control */ +#define CMDREG_SW 2 /* Size write */ +#define CMDREG_SR 4 /* Size read */ +#define CMDREG_RS 8 /* Reset interface */ +#define CMDREG_FRIE 0x40 /* Enable FR interrupt */ +#define CMDREG_DAIE 0x80 /* Enable DA interrupt */ +#define IRQEN (CMDREG_DAIE) + +#define STATUSREG_RE 1 /* read error */ +#define STATUSREG_WE 2 /* write error */ +#define STATUSREG_FR 0x40 /* module free */ +#define STATUSREG_DA 0x80 /* data available */ + +#define DVB_CA_SLOTSTATE_NONE 0 +#define DVB_CA_SLOTSTATE_UNINITIALISED 1 +#define DVB_CA_SLOTSTATE_RUNNING 2 +#define DVB_CA_SLOTSTATE_INVALID 3 +#define DVB_CA_SLOTSTATE_WAITREADY 4 +#define DVB_CA_SLOTSTATE_VALIDATE 5 +#define DVB_CA_SLOTSTATE_WAITFR 6 +#define DVB_CA_SLOTSTATE_LINKINIT 7 + +/* Information on a CA slot */ +struct dvb_ca_slot { + /* current state of the CAM */ + int slot_state; + + /* mutex used for serializing access to one CI slot */ + struct mutex slot_lock; + + /* Number of CAMCHANGES that have occurred since last processing */ + atomic_t camchange_count; + + /* Type of last CAMCHANGE */ + int camchange_type; + + /* base address of CAM config */ + u32 config_base; + + /* value to write into Config Control register */ + u8 config_option; + + /* if 1, the CAM supports DA IRQs */ + u8 da_irq_supported:1; + + /* size of the buffer to use when talking to the CAM */ + int link_buf_size; + + /* buffer for incoming packets */ + struct dvb_ringbuffer rx_buffer; + + /* timer used during various states of the slot */ + unsigned long timeout; +}; + +/* Private CA-interface information */ +struct dvb_ca_private { + struct kref refcount; + + /* pointer back to the public data structure */ + struct dvb_ca_en50221 *pub; + + /* the DVB device */ + struct dvb_device *dvbdev; + + /* Flags describing the interface (DVB_CA_FLAG_*) */ + u32 flags; + + /* number of slots supported by this CA interface */ + unsigned int slot_count; + + /* information on each slot */ + struct dvb_ca_slot *slot_info; + + /* wait queues for read() and write() operations */ + wait_queue_head_t wait_queue; + + /* PID of the monitoring thread */ + struct task_struct *thread; + + /* Flag indicating if the CA device is open */ + unsigned int open:1; + + /* Flag indicating the thread should wake up now */ + unsigned int wakeup:1; + + /* Delay the main thread should use */ + unsigned long delay; + + /* + * Slot to start looking for data to read from in the next user-space + * read operation + */ + int next_read_slot; + + /* mutex serializing ioctls */ + struct mutex ioctl_mutex; + + /* A mutex used when a device is disconnected */ + struct mutex remove_mutex; + + /* Whether the device is disconnected */ + int exit; +}; + +static void dvb_ca_private_free(struct dvb_ca_private *ca) +{ + unsigned int i; + + dvb_device_put(ca->dvbdev); + for (i = 0; i < ca->slot_count; i++) + vfree(ca->slot_info[i].rx_buffer.data); + + kfree(ca->slot_info); + kfree(ca); +} + +static void dvb_ca_private_release(struct kref *ref) +{ + struct dvb_ca_private *ca; + + ca = container_of(ref, struct dvb_ca_private, refcount); + dvb_ca_private_free(ca); +} + +static void dvb_ca_private_get(struct dvb_ca_private *ca) +{ + kref_get(&ca->refcount); +} + +static void dvb_ca_private_put(struct dvb_ca_private *ca) +{ + kref_put(&ca->refcount, dvb_ca_private_release); +} + +static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca); +static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, + u8 *ebuf, int ecount); +static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, + u8 *ebuf, int ecount, int size_write_flag); + +/** + * findstr - Safely find needle in haystack. + * + * @haystack: Buffer to look in. + * @hlen: Number of bytes in haystack. + * @needle: Buffer to find. + * @nlen: Number of bytes in needle. + * return: Pointer into haystack needle was found at, or NULL if not found. + */ +static char *findstr(char *haystack, int hlen, char *needle, int nlen) +{ + int i; + + if (hlen < nlen) + return NULL; + + for (i = 0; i <= hlen - nlen; i++) { + if (!strncmp(haystack + i, needle, nlen)) + return haystack + i; + } + + return NULL; +} + +/* ************************************************************************** */ +/* EN50221 physical interface functions */ + +/* + * dvb_ca_en50221_check_camstatus - Check CAM status. + */ +static int dvb_ca_en50221_check_camstatus(struct dvb_ca_private *ca, int slot) +{ + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + int slot_status; + int cam_present_now; + int cam_changed; + + /* IRQ mode */ + if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE) + return (atomic_read(&sl->camchange_count) != 0); + + /* poll mode */ + slot_status = ca->pub->poll_slot_status(ca->pub, slot, ca->open); + + cam_present_now = (slot_status & DVB_CA_EN50221_POLL_CAM_PRESENT) ? 1 : 0; + cam_changed = (slot_status & DVB_CA_EN50221_POLL_CAM_CHANGED) ? 1 : 0; + if (!cam_changed) { + int cam_present_old = (sl->slot_state != DVB_CA_SLOTSTATE_NONE); + + cam_changed = (cam_present_now != cam_present_old); + } + + if (cam_changed) { + if (!cam_present_now) + sl->camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED; + else + sl->camchange_type = DVB_CA_EN50221_CAMCHANGE_INSERTED; + atomic_set(&sl->camchange_count, 1); + } else { + if ((sl->slot_state == DVB_CA_SLOTSTATE_WAITREADY) && + (slot_status & DVB_CA_EN50221_POLL_CAM_READY)) { + /* move to validate state if reset is completed */ + sl->slot_state = DVB_CA_SLOTSTATE_VALIDATE; + } + } + + return cam_changed; +} + +/** + * dvb_ca_en50221_wait_if_status - Wait for flags to become set on the STATUS + * register on a CAM interface, checking for errors and timeout. + * + * @ca: CA instance. + * @slot: Slot on interface. + * @waitfor: Flags to wait for. + * @timeout_hz: Timeout in milliseconds. + * + * return: 0 on success, nonzero on error. + */ +static int dvb_ca_en50221_wait_if_status(struct dvb_ca_private *ca, int slot, + u8 waitfor, int timeout_hz) +{ + unsigned long timeout; + unsigned long start; + + dprintk("%s\n", __func__); + + /* loop until timeout elapsed */ + start = jiffies; + timeout = jiffies + timeout_hz; + while (1) { + int res; + + /* read the status and check for error */ + res = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (res < 0) + return -EIO; + + /* if we got the flags, it was successful! */ + if (res & waitfor) { + dprintk("%s succeeded timeout:%lu\n", + __func__, jiffies - start); + return 0; + } + + /* check for timeout */ + if (time_after(jiffies, timeout)) + break; + + /* wait for a bit */ + usleep_range(1000, 1100); + } + + dprintk("%s failed timeout:%lu\n", __func__, jiffies - start); + + /* if we get here, we've timed out */ + return -ETIMEDOUT; +} + +/** + * dvb_ca_en50221_link_init - Initialise the link layer connection to a CAM. + * + * @ca: CA instance. + * @slot: Slot id. + * + * return: 0 on success, nonzero on failure. + */ +static int dvb_ca_en50221_link_init(struct dvb_ca_private *ca, int slot) +{ + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + int ret; + int buf_size; + u8 buf[2]; + + dprintk("%s\n", __func__); + + /* we'll be determining these during this function */ + sl->da_irq_supported = 0; + + /* + * set the host link buffer size temporarily. it will be overwritten + * with the real negotiated size later. + */ + sl->link_buf_size = 2; + + /* read the buffer size from the CAM */ + ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, + IRQEN | CMDREG_SR); + if (ret) + return ret; + ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_DA, HZ); + if (ret) + return ret; + ret = dvb_ca_en50221_read_data(ca, slot, buf, 2); + if (ret != 2) + return -EIO; + ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN); + if (ret) + return ret; + + /* + * store it, and choose the minimum of our buffer and the CAM's buffer + * size + */ + buf_size = (buf[0] << 8) | buf[1]; + if (buf_size > HOST_LINK_BUF_SIZE) + buf_size = HOST_LINK_BUF_SIZE; + sl->link_buf_size = buf_size; + buf[0] = buf_size >> 8; + buf[1] = buf_size & 0xff; + dprintk("Chosen link buffer size of %i\n", buf_size); + + /* write the buffer size to the CAM */ + ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, + IRQEN | CMDREG_SW); + if (ret) + return ret; + ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_FR, HZ / 10); + if (ret) + return ret; + ret = dvb_ca_en50221_write_data(ca, slot, buf, 2, CMDREG_SW); + if (ret != 2) + return -EIO; + ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN); + if (ret) + return ret; + + /* success */ + return 0; +} + +/** + * dvb_ca_en50221_read_tuple - Read a tuple from attribute memory. + * + * @ca: CA instance. + * @slot: Slot id. + * @address: Address to read from. Updated. + * @tuple_type: Tuple id byte. Updated. + * @tuple_length: Tuple length. Updated. + * @tuple: Dest buffer for tuple (must be 256 bytes). Updated. + * + * return: 0 on success, nonzero on error. + */ +static int dvb_ca_en50221_read_tuple(struct dvb_ca_private *ca, int slot, + int *address, int *tuple_type, + int *tuple_length, u8 *tuple) +{ + int i; + int _tuple_type; + int _tuple_length; + int _address = *address; + + /* grab the next tuple length and type */ + _tuple_type = ca->pub->read_attribute_mem(ca->pub, slot, _address); + if (_tuple_type < 0) + return _tuple_type; + if (_tuple_type == 0xff) { + dprintk("END OF CHAIN TUPLE type:0x%x\n", _tuple_type); + *address += 2; + *tuple_type = _tuple_type; + *tuple_length = 0; + return 0; + } + _tuple_length = ca->pub->read_attribute_mem(ca->pub, slot, + _address + 2); + if (_tuple_length < 0) + return _tuple_length; + _address += 4; + + dprintk("TUPLE type:0x%x length:%i\n", _tuple_type, _tuple_length); + + /* read in the whole tuple */ + for (i = 0; i < _tuple_length; i++) { + tuple[i] = ca->pub->read_attribute_mem(ca->pub, slot, + _address + (i * 2)); + dprintk(" 0x%02x: 0x%02x %c\n", + i, tuple[i] & 0xff, + ((tuple[i] > 31) && (tuple[i] < 127)) ? tuple[i] : '.'); + } + _address += (_tuple_length * 2); + + /* success */ + *tuple_type = _tuple_type; + *tuple_length = _tuple_length; + *address = _address; + return 0; +} + +/** + * dvb_ca_en50221_parse_attributes - Parse attribute memory of a CAM module, + * extracting Config register, and checking it is a DVB CAM module. + * + * @ca: CA instance. + * @slot: Slot id. + * + * return: 0 on success, <0 on failure. + */ +static int dvb_ca_en50221_parse_attributes(struct dvb_ca_private *ca, int slot) +{ + struct dvb_ca_slot *sl; + int address = 0; + int tuple_length; + int tuple_type; + u8 tuple[257]; + char *dvb_str; + int rasz; + int status; + int got_cftableentry = 0; + int end_chain = 0; + int i; + u16 manfid = 0; + u16 devid = 0; + + /* CISTPL_DEVICE_0A */ + status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type, + &tuple_length, tuple); + if (status < 0) + return status; + if (tuple_type != 0x1D) + return -EINVAL; + + /* CISTPL_DEVICE_0C */ + status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type, + &tuple_length, tuple); + if (status < 0) + return status; + if (tuple_type != 0x1C) + return -EINVAL; + + /* CISTPL_VERS_1 */ + status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type, + &tuple_length, tuple); + if (status < 0) + return status; + if (tuple_type != 0x15) + return -EINVAL; + + /* CISTPL_MANFID */ + status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type, + &tuple_length, tuple); + if (status < 0) + return status; + if (tuple_type != 0x20) + return -EINVAL; + if (tuple_length != 4) + return -EINVAL; + manfid = (tuple[1] << 8) | tuple[0]; + devid = (tuple[3] << 8) | tuple[2]; + + /* CISTPL_CONFIG */ + status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type, + &tuple_length, tuple); + if (status < 0) + return status; + if (tuple_type != 0x1A) + return -EINVAL; + if (tuple_length < 3) + return -EINVAL; + + /* extract the configbase */ + rasz = tuple[0] & 3; + if (tuple_length < (3 + rasz + 14)) + return -EINVAL; + sl = &ca->slot_info[slot]; + sl->config_base = 0; + for (i = 0; i < rasz + 1; i++) + sl->config_base |= (tuple[2 + i] << (8 * i)); + + /* check it contains the correct DVB string */ + dvb_str = findstr((char *)tuple, tuple_length, "DVB_CI_V", 8); + if (!dvb_str) + return -EINVAL; + if (tuple_length < ((dvb_str - (char *)tuple) + 12)) + return -EINVAL; + + /* is it a version we support? */ + if (strncmp(dvb_str + 8, "1.00", 4)) { + pr_err("dvb_ca adapter %d: Unsupported DVB CAM module version %c%c%c%c\n", + ca->dvbdev->adapter->num, dvb_str[8], dvb_str[9], + dvb_str[10], dvb_str[11]); + return -EINVAL; + } + + /* process the CFTABLE_ENTRY tuples, and any after those */ + while ((!end_chain) && (address < 0x1000)) { + status = dvb_ca_en50221_read_tuple(ca, slot, &address, + &tuple_type, &tuple_length, + tuple); + if (status < 0) + return status; + switch (tuple_type) { + case 0x1B: /* CISTPL_CFTABLE_ENTRY */ + if (tuple_length < (2 + 11 + 17)) + break; + + /* if we've already parsed one, just use it */ + if (got_cftableentry) + break; + + /* get the config option */ + sl->config_option = tuple[0] & 0x3f; + + /* OK, check it contains the correct strings */ + if (!findstr((char *)tuple, tuple_length, + "DVB_HOST", 8) || + !findstr((char *)tuple, tuple_length, + "DVB_CI_MODULE", 13)) + break; + + got_cftableentry = 1; + break; + + case 0x14: /* CISTPL_NO_LINK */ + break; + + case 0xFF: /* CISTPL_END */ + end_chain = 1; + break; + + default: /* Unknown tuple type - just skip this tuple */ + dprintk("dvb_ca: Skipping unknown tuple type:0x%x length:0x%x\n", + tuple_type, tuple_length); + break; + } + } + + if ((address > 0x1000) || (!got_cftableentry)) + return -EINVAL; + + dprintk("Valid DVB CAM detected MANID:%x DEVID:%x CONFIGBASE:0x%x CONFIGOPTION:0x%x\n", + manfid, devid, sl->config_base, sl->config_option); + + /* success! */ + return 0; +} + +/** + * dvb_ca_en50221_set_configoption - Set CAM's configoption correctly. + * + * @ca: CA instance. + * @slot: Slot containing the CAM. + */ +static int dvb_ca_en50221_set_configoption(struct dvb_ca_private *ca, int slot) +{ + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + int configoption; + + dprintk("%s\n", __func__); + + /* set the config option */ + ca->pub->write_attribute_mem(ca->pub, slot, sl->config_base, + sl->config_option); + + /* check it */ + configoption = ca->pub->read_attribute_mem(ca->pub, slot, + sl->config_base); + dprintk("Set configoption 0x%x, read configoption 0x%x\n", + sl->config_option, configoption & 0x3f); + + /* fine! */ + return 0; +} + +/** + * dvb_ca_en50221_read_data - This function talks to an EN50221 CAM control + * interface. It reads a buffer of data from the CAM. The data can either + * be stored in a supplied buffer, or automatically be added to the slot's + * rx_buffer. + * + * @ca: CA instance. + * @slot: Slot to read from. + * @ebuf: If non-NULL, the data will be written to this buffer. If NULL, + * the data will be added into the buffering system as a normal + * fragment. + * @ecount: Size of ebuf. Ignored if ebuf is NULL. + * + * return: Number of bytes read, or < 0 on error + */ +static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, + u8 *ebuf, int ecount) +{ + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + int bytes_read; + int status; + u8 buf[HOST_LINK_BUF_SIZE]; + int i; + + dprintk("%s\n", __func__); + + /* check if we have space for a link buf in the rx_buffer */ + if (!ebuf) { + int buf_free; + + if (!sl->rx_buffer.data) { + status = -EIO; + goto exit; + } + buf_free = dvb_ringbuffer_free(&sl->rx_buffer); + + if (buf_free < (sl->link_buf_size + + DVB_RINGBUFFER_PKTHDRSIZE)) { + status = -EAGAIN; + goto exit; + } + } + + if (ca->pub->read_data && + (sl->slot_state != DVB_CA_SLOTSTATE_LINKINIT)) { + if (!ebuf) + status = ca->pub->read_data(ca->pub, slot, buf, + sizeof(buf)); + else + status = ca->pub->read_data(ca->pub, slot, buf, ecount); + if (status < 0) + return status; + bytes_read = status; + if (status == 0) + goto exit; + } else { + /* check if there is data available */ + status = ca->pub->read_cam_control(ca->pub, slot, + CTRLIF_STATUS); + if (status < 0) + goto exit; + if (!(status & STATUSREG_DA)) { + /* no data */ + status = 0; + goto exit; + } + + /* read the amount of data */ + status = ca->pub->read_cam_control(ca->pub, slot, + CTRLIF_SIZE_HIGH); + if (status < 0) + goto exit; + bytes_read = status << 8; + status = ca->pub->read_cam_control(ca->pub, slot, + CTRLIF_SIZE_LOW); + if (status < 0) + goto exit; + bytes_read |= status; + + /* check it will fit */ + if (!ebuf) { + if (bytes_read > sl->link_buf_size) { + pr_err("dvb_ca adapter %d: CAM tried to send a buffer larger than the link buffer size (%i > %i)!\n", + ca->dvbdev->adapter->num, bytes_read, + sl->link_buf_size); + sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + if (bytes_read < 2) { + pr_err("dvb_ca adapter %d: CAM sent a buffer that was less than 2 bytes!\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + } else { + if (bytes_read > ecount) { + pr_err("dvb_ca adapter %d: CAM tried to send a buffer larger than the ecount size!\n", + ca->dvbdev->adapter->num); + status = -EIO; + goto exit; + } + } + + /* fill the buffer */ + for (i = 0; i < bytes_read; i++) { + /* read byte and check */ + status = ca->pub->read_cam_control(ca->pub, slot, + CTRLIF_DATA); + if (status < 0) + goto exit; + + /* OK, store it in the buffer */ + buf[i] = status; + } + + /* check for read error (RE should now be 0) */ + status = ca->pub->read_cam_control(ca->pub, slot, + CTRLIF_STATUS); + if (status < 0) + goto exit; + if (status & STATUSREG_RE) { + sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + } + + /* + * OK, add it to the receive buffer, or copy into external buffer if + * supplied + */ + if (!ebuf) { + if (!sl->rx_buffer.data) { + status = -EIO; + goto exit; + } + dvb_ringbuffer_pkt_write(&sl->rx_buffer, buf, bytes_read); + } else { + memcpy(ebuf, buf, bytes_read); + } + + dprintk("Received CA packet for slot %i connection id 0x%x last_frag:%i size:0x%x\n", slot, + buf[0], (buf[1] & 0x80) == 0, bytes_read); + + /* wake up readers when a last_fragment is received */ + if ((buf[1] & 0x80) == 0x00) + wake_up_interruptible(&ca->wait_queue); + + status = bytes_read; + +exit: + return status; +} + +/** + * dvb_ca_en50221_write_data - This function talks to an EN50221 CAM control + * interface. It writes a buffer of data to a CAM. + * + * @ca: CA instance. + * @slot: Slot to write to. + * @buf: The data in this buffer is treated as a complete link-level packet to + * be written. + * @bytes_write: Size of ebuf. + * @size_write_flag: A flag on Command Register which says whether the link size + * information will be writen or not. + * + * return: Number of bytes written, or < 0 on error. + */ +static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, + u8 *buf, int bytes_write, int size_write_flag) +{ + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + int status; + int i; + + dprintk("%s\n", __func__); + + /* sanity check */ + if (bytes_write > sl->link_buf_size) + return -EINVAL; + + if (ca->pub->write_data && + (sl->slot_state != DVB_CA_SLOTSTATE_LINKINIT)) + return ca->pub->write_data(ca->pub, slot, buf, bytes_write); + + /* + * it is possible we are dealing with a single buffer implementation, + * thus if there is data available for read or if there is even a read + * already in progress, we do nothing but awake the kernel thread to + * process the data if necessary. + */ + status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (status < 0) + goto exitnowrite; + if (status & (STATUSREG_DA | STATUSREG_RE)) { + if (status & STATUSREG_DA) + dvb_ca_en50221_thread_wakeup(ca); + + status = -EAGAIN; + goto exitnowrite; + } + + /* OK, set HC bit */ + status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, + IRQEN | CMDREG_HC | size_write_flag); + if (status) + goto exit; + + /* check if interface is still free */ + status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (status < 0) + goto exit; + if (!(status & STATUSREG_FR)) { + /* it wasn't free => try again later */ + status = -EAGAIN; + goto exit; + } + + /* + * It may need some time for the CAM to settle down, or there might + * be a race condition between the CAM, writing HC and our last + * check for DA. This happens, if the CAM asserts DA, just after + * checking DA before we are setting HC. In this case it might be + * a bug in the CAM to keep the FR bit, the lower layer/HW + * communication requires a longer timeout or the CAM needs more + * time internally. But this happens in reality! + * We need to read the status from the HW again and do the same + * we did for the previous check for DA + */ + status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (status < 0) + goto exit; + + if (status & (STATUSREG_DA | STATUSREG_RE)) { + if (status & STATUSREG_DA) + dvb_ca_en50221_thread_wakeup(ca); + + status = -EAGAIN; + goto exit; + } + + /* send the amount of data */ + status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_HIGH, + bytes_write >> 8); + if (status) + goto exit; + status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_LOW, + bytes_write & 0xff); + if (status) + goto exit; + + /* send the buffer */ + for (i = 0; i < bytes_write; i++) { + status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_DATA, + buf[i]); + if (status) + goto exit; + } + + /* check for write error (WE should now be 0) */ + status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (status < 0) + goto exit; + if (status & STATUSREG_WE) { + sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + status = bytes_write; + + dprintk("Wrote CA packet for slot %i, connection id 0x%x last_frag:%i size:0x%x\n", slot, + buf[0], (buf[1] & 0x80) == 0, bytes_write); + +exit: + ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN); + +exitnowrite: + return status; +} + +/* ************************************************************************** */ +/* EN50221 higher level functions */ + +/** + * dvb_ca_en50221_slot_shutdown - A CAM has been removed => shut it down. + * + * @ca: CA instance. + * @slot: Slot to shut down. + */ +static int dvb_ca_en50221_slot_shutdown(struct dvb_ca_private *ca, int slot) +{ + dprintk("%s\n", __func__); + + ca->pub->slot_shutdown(ca->pub, slot); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_NONE; + + /* + * need to wake up all processes to check if they're now trying to + * write to a defunct CAM + */ + wake_up_interruptible(&ca->wait_queue); + + dprintk("Slot %i shutdown\n", slot); + + /* success */ + return 0; +} + +/** + * dvb_ca_en50221_camchange_irq - A CAMCHANGE IRQ has occurred. + * + * @pubca: CA instance. + * @slot: Slot concerned. + * @change_type: One of the DVB_CA_CAMCHANGE_* values. + */ +void dvb_ca_en50221_camchange_irq(struct dvb_ca_en50221 *pubca, int slot, + int change_type) +{ + struct dvb_ca_private *ca = pubca->private; + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + + dprintk("CAMCHANGE IRQ slot:%i change_type:%i\n", slot, change_type); + + switch (change_type) { + case DVB_CA_EN50221_CAMCHANGE_REMOVED: + case DVB_CA_EN50221_CAMCHANGE_INSERTED: + break; + + default: + return; + } + + sl->camchange_type = change_type; + atomic_inc(&sl->camchange_count); + dvb_ca_en50221_thread_wakeup(ca); +} +EXPORT_SYMBOL(dvb_ca_en50221_camchange_irq); + +/** + * dvb_ca_en50221_camready_irq - A CAMREADY IRQ has occurred. + * + * @pubca: CA instance. + * @slot: Slot concerned. + */ +void dvb_ca_en50221_camready_irq(struct dvb_ca_en50221 *pubca, int slot) +{ + struct dvb_ca_private *ca = pubca->private; + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + + dprintk("CAMREADY IRQ slot:%i\n", slot); + + if (sl->slot_state == DVB_CA_SLOTSTATE_WAITREADY) { + sl->slot_state = DVB_CA_SLOTSTATE_VALIDATE; + dvb_ca_en50221_thread_wakeup(ca); + } +} +EXPORT_SYMBOL(dvb_ca_en50221_camready_irq); + +/** + * dvb_ca_en50221_frda_irq - An FR or DA IRQ has occurred. + * + * @pubca: CA instance. + * @slot: Slot concerned. + */ +void dvb_ca_en50221_frda_irq(struct dvb_ca_en50221 *pubca, int slot) +{ + struct dvb_ca_private *ca = pubca->private; + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + int flags; + + dprintk("FR/DA IRQ slot:%i\n", slot); + + switch (sl->slot_state) { + case DVB_CA_SLOTSTATE_LINKINIT: + flags = ca->pub->read_cam_control(pubca, slot, CTRLIF_STATUS); + if (flags & STATUSREG_DA) { + dprintk("CAM supports DA IRQ\n"); + sl->da_irq_supported = 1; + } + break; + + case DVB_CA_SLOTSTATE_RUNNING: + if (ca->open) + dvb_ca_en50221_thread_wakeup(ca); + break; + } +} +EXPORT_SYMBOL(dvb_ca_en50221_frda_irq); + +/* ************************************************************************** */ +/* EN50221 thread functions */ + +/** + * dvb_ca_en50221_thread_wakeup - Wake up the DVB CA thread + * + * @ca: CA instance. + */ +static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca) +{ + dprintk("%s\n", __func__); + + ca->wakeup = 1; + mb(); + wake_up_process(ca->thread); +} + +/** + * dvb_ca_en50221_thread_update_delay - Update the delay used by the thread. + * + * @ca: CA instance. + */ +static void dvb_ca_en50221_thread_update_delay(struct dvb_ca_private *ca) +{ + int delay; + int curdelay = 100000000; + int slot; + + /* + * Beware of too high polling frequency, because one polling + * call might take several hundred milliseconds until timeout! + */ + for (slot = 0; slot < ca->slot_count; slot++) { + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + + switch (sl->slot_state) { + default: + case DVB_CA_SLOTSTATE_NONE: + delay = HZ * 60; /* 60s */ + if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)) + delay = HZ * 5; /* 5s */ + break; + case DVB_CA_SLOTSTATE_INVALID: + delay = HZ * 60; /* 60s */ + if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)) + delay = HZ / 10; /* 100ms */ + break; + + case DVB_CA_SLOTSTATE_UNINITIALISED: + case DVB_CA_SLOTSTATE_WAITREADY: + case DVB_CA_SLOTSTATE_VALIDATE: + case DVB_CA_SLOTSTATE_WAITFR: + case DVB_CA_SLOTSTATE_LINKINIT: + delay = HZ / 10; /* 100ms */ + break; + + case DVB_CA_SLOTSTATE_RUNNING: + delay = HZ * 60; /* 60s */ + if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)) + delay = HZ / 10; /* 100ms */ + if (ca->open) { + if ((!sl->da_irq_supported) || + (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_DA))) + delay = HZ / 10; /* 100ms */ + } + break; + } + + if (delay < curdelay) + curdelay = delay; + } + + ca->delay = curdelay; +} + +/** + * dvb_ca_en50221_poll_cam_gone - Poll if the CAM is gone. + * + * @ca: CA instance. + * @slot: Slot to process. + * return:: 0 .. no change + * 1 .. CAM state changed + */ + +static int dvb_ca_en50221_poll_cam_gone(struct dvb_ca_private *ca, int slot) +{ + int changed = 0; + int status; + + /* + * we need this extra check for annoying interfaces like the + * budget-av + */ + if ((!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)) && + (ca->pub->poll_slot_status)) { + status = ca->pub->poll_slot_status(ca->pub, slot, 0); + if (!(status & + DVB_CA_EN50221_POLL_CAM_PRESENT)) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_NONE; + dvb_ca_en50221_thread_update_delay(ca); + changed = 1; + } + } + return changed; +} + +/** + * dvb_ca_en50221_thread_state_machine - Thread state machine for one CA slot + * to perform the data transfer. + * + * @ca: CA instance. + * @slot: Slot to process. + */ +static void dvb_ca_en50221_thread_state_machine(struct dvb_ca_private *ca, + int slot) +{ + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + int flags; + int pktcount; + void *rxbuf; + + mutex_lock(&sl->slot_lock); + + /* check the cam status + deal with CAMCHANGEs */ + while (dvb_ca_en50221_check_camstatus(ca, slot)) { + /* clear down an old CI slot if necessary */ + if (sl->slot_state != DVB_CA_SLOTSTATE_NONE) + dvb_ca_en50221_slot_shutdown(ca, slot); + + /* if a CAM is NOW present, initialise it */ + if (sl->camchange_type == DVB_CA_EN50221_CAMCHANGE_INSERTED) + sl->slot_state = DVB_CA_SLOTSTATE_UNINITIALISED; + + /* we've handled one CAMCHANGE */ + dvb_ca_en50221_thread_update_delay(ca); + atomic_dec(&sl->camchange_count); + } + + /* CAM state machine */ + switch (sl->slot_state) { + case DVB_CA_SLOTSTATE_NONE: + case DVB_CA_SLOTSTATE_INVALID: + /* no action needed */ + break; + + case DVB_CA_SLOTSTATE_UNINITIALISED: + sl->slot_state = DVB_CA_SLOTSTATE_WAITREADY; + ca->pub->slot_reset(ca->pub, slot); + sl->timeout = jiffies + (INIT_TIMEOUT_SECS * HZ); + break; + + case DVB_CA_SLOTSTATE_WAITREADY: + if (time_after(jiffies, sl->timeout)) { + pr_err("dvb_ca adaptor %d: PC card did not respond :(\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + /* + * no other action needed; will automatically change state when + * ready + */ + break; + + case DVB_CA_SLOTSTATE_VALIDATE: + if (dvb_ca_en50221_parse_attributes(ca, slot) != 0) { + if (dvb_ca_en50221_poll_cam_gone(ca, slot)) + break; + + pr_err("dvb_ca adapter %d: Invalid PC card inserted :(\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + if (dvb_ca_en50221_set_configoption(ca, slot) != 0) { + pr_err("dvb_ca adapter %d: Unable to initialise CAM :(\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + if (ca->pub->write_cam_control(ca->pub, slot, + CTRLIF_COMMAND, + CMDREG_RS) != 0) { + pr_err("dvb_ca adapter %d: Unable to reset CAM IF\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + dprintk("DVB CAM validated successfully\n"); + + sl->timeout = jiffies + (INIT_TIMEOUT_SECS * HZ); + sl->slot_state = DVB_CA_SLOTSTATE_WAITFR; + ca->wakeup = 1; + break; + + case DVB_CA_SLOTSTATE_WAITFR: + if (time_after(jiffies, sl->timeout)) { + pr_err("dvb_ca adapter %d: DVB CAM did not respond :(\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + + flags = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (flags & STATUSREG_FR) { + sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT; + ca->wakeup = 1; + } + break; + + case DVB_CA_SLOTSTATE_LINKINIT: + if (dvb_ca_en50221_link_init(ca, slot) != 0) { + if (dvb_ca_en50221_poll_cam_gone(ca, slot)) + break; + + pr_err("dvb_ca adapter %d: DVB CAM link initialisation failed :(\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_UNINITIALISED; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + + if (!sl->rx_buffer.data) { + rxbuf = vmalloc(RX_BUFFER_SIZE); + if (!rxbuf) { + pr_err("dvb_ca adapter %d: Unable to allocate CAM rx buffer :(\n", + ca->dvbdev->adapter->num); + sl->slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + dvb_ringbuffer_init(&sl->rx_buffer, rxbuf, + RX_BUFFER_SIZE); + } + + ca->pub->slot_ts_enable(ca->pub, slot); + sl->slot_state = DVB_CA_SLOTSTATE_RUNNING; + dvb_ca_en50221_thread_update_delay(ca); + pr_info("dvb_ca adapter %d: DVB CAM detected and initialised successfully\n", + ca->dvbdev->adapter->num); + break; + + case DVB_CA_SLOTSTATE_RUNNING: + if (!ca->open) + break; + + /* poll slots for data */ + pktcount = 0; + while (dvb_ca_en50221_read_data(ca, slot, NULL, 0) > 0) { + if (!ca->open) + break; + + /* + * if a CAMCHANGE occurred at some point, do not do any + * more processing of this slot + */ + if (dvb_ca_en50221_check_camstatus(ca, slot)) { + /* + * we don't want to sleep on the next iteration + * so we can handle the cam change + */ + ca->wakeup = 1; + break; + } + + /* check if we've hit our limit this time */ + if (++pktcount >= MAX_RX_PACKETS_PER_ITERATION) { + /* + * don't sleep; there is likely to be more data + * to read + */ + ca->wakeup = 1; + break; + } + } + break; + } + + mutex_unlock(&sl->slot_lock); +} + +/* + * Kernel thread which monitors CA slots for CAM changes, and performs data + * transfers. + */ +static int dvb_ca_en50221_thread(void *data) +{ + struct dvb_ca_private *ca = data; + int slot; + + dprintk("%s\n", __func__); + + /* choose the correct initial delay */ + dvb_ca_en50221_thread_update_delay(ca); + + /* main loop */ + while (!kthread_should_stop()) { + /* sleep for a bit */ + if (!ca->wakeup) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(ca->delay); + if (kthread_should_stop()) + return 0; + } + ca->wakeup = 0; + + /* go through all the slots processing them */ + for (slot = 0; slot < ca->slot_count; slot++) + dvb_ca_en50221_thread_state_machine(ca, slot); + } + + return 0; +} + +/* ************************************************************************** */ +/* EN50221 IO interface functions */ + +/** + * dvb_ca_en50221_io_do_ioctl - Real ioctl implementation. + * + * @file: File concerned. + * @cmd: IOCTL command. + * @parg: Associated argument. + * + * NOTE: CA_SEND_MSG/CA_GET_MSG ioctls have userspace buffers passed to them. + * + * return: 0 on success, <0 on error. + */ +static int dvb_ca_en50221_io_do_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_ca_private *ca = dvbdev->priv; + int err = 0; + int slot; + + dprintk("%s\n", __func__); + + if (mutex_lock_interruptible(&ca->ioctl_mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case CA_RESET: + for (slot = 0; slot < ca->slot_count; slot++) { + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + + mutex_lock(&sl->slot_lock); + if (sl->slot_state != DVB_CA_SLOTSTATE_NONE) { + dvb_ca_en50221_slot_shutdown(ca, slot); + if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE) + dvb_ca_en50221_camchange_irq(ca->pub, + slot, + DVB_CA_EN50221_CAMCHANGE_INSERTED); + } + mutex_unlock(&sl->slot_lock); + } + ca->next_read_slot = 0; + dvb_ca_en50221_thread_wakeup(ca); + break; + + case CA_GET_CAP: { + struct ca_caps *caps = parg; + + caps->slot_num = ca->slot_count; + caps->slot_type = CA_CI_LINK; + caps->descr_num = 0; + caps->descr_type = 0; + break; + } + + case CA_GET_SLOT_INFO: { + struct ca_slot_info *info = parg; + struct dvb_ca_slot *sl; + + slot = info->num; + if ((slot >= ca->slot_count) || (slot < 0)) { + err = -EINVAL; + goto out_unlock; + } + slot = array_index_nospec(slot, ca->slot_count); + + info->type = CA_CI_LINK; + info->flags = 0; + sl = &ca->slot_info[slot]; + if ((sl->slot_state != DVB_CA_SLOTSTATE_NONE) && + (sl->slot_state != DVB_CA_SLOTSTATE_INVALID)) { + info->flags = CA_CI_MODULE_PRESENT; + } + if (sl->slot_state == DVB_CA_SLOTSTATE_RUNNING) + info->flags |= CA_CI_MODULE_READY; + break; + } + + default: + err = -EINVAL; + break; + } + +out_unlock: + mutex_unlock(&ca->ioctl_mutex); + return err; +} + +/** + * dvb_ca_en50221_io_ioctl - Wrapper for ioctl implementation. + * + * @file: File concerned. + * @cmd: IOCTL command. + * @arg: Associated argument. + * + * return: 0 on success, <0 on error. + */ +static long dvb_ca_en50221_io_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(file, cmd, arg, dvb_ca_en50221_io_do_ioctl); +} + +/** + * dvb_ca_en50221_io_write - Implementation of write() syscall. + * + * @file: File structure. + * @buf: Source buffer. + * @count: Size of source buffer. + * @ppos: Position in file (ignored). + * + * return: Number of bytes read, or <0 on error. + */ +static ssize_t dvb_ca_en50221_io_write(struct file *file, + const char __user *buf, size_t count, + loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_ca_private *ca = dvbdev->priv; + struct dvb_ca_slot *sl; + u8 slot, connection_id; + int status; + u8 fragbuf[HOST_LINK_BUF_SIZE]; + int fragpos = 0; + int fraglen; + unsigned long timeout; + int written; + + dprintk("%s\n", __func__); + + /* + * Incoming packet has a 2 byte header. + * hdr[0] = slot_id, hdr[1] = connection_id + */ + if (count < 2) + return -EINVAL; + + /* extract slot & connection id */ + if (copy_from_user(&slot, buf, 1)) + return -EFAULT; + if (copy_from_user(&connection_id, buf + 1, 1)) + return -EFAULT; + buf += 2; + count -= 2; + + if (slot >= ca->slot_count) + return -EINVAL; + slot = array_index_nospec(slot, ca->slot_count); + sl = &ca->slot_info[slot]; + + /* check if the slot is actually running */ + if (sl->slot_state != DVB_CA_SLOTSTATE_RUNNING) + return -EINVAL; + + /* fragment the packets & store in the buffer */ + while (fragpos < count) { + fraglen = sl->link_buf_size - 2; + if (fraglen < 0) + break; + if (fraglen > HOST_LINK_BUF_SIZE - 2) + fraglen = HOST_LINK_BUF_SIZE - 2; + if ((count - fragpos) < fraglen) + fraglen = count - fragpos; + + fragbuf[0] = connection_id; + fragbuf[1] = ((fragpos + fraglen) < count) ? 0x80 : 0x00; + status = copy_from_user(fragbuf + 2, buf + fragpos, fraglen); + if (status) { + status = -EFAULT; + goto exit; + } + + timeout = jiffies + HZ / 2; + written = 0; + while (!time_after(jiffies, timeout)) { + /* + * check the CAM hasn't been removed/reset in the + * meantime + */ + if (sl->slot_state != DVB_CA_SLOTSTATE_RUNNING) { + status = -EIO; + goto exit; + } + + mutex_lock(&sl->slot_lock); + status = dvb_ca_en50221_write_data(ca, slot, fragbuf, + fraglen + 2, 0); + mutex_unlock(&sl->slot_lock); + if (status == (fraglen + 2)) { + written = 1; + break; + } + if (status != -EAGAIN) + goto exit; + + usleep_range(1000, 1100); + } + if (!written) { + status = -EIO; + goto exit; + } + + fragpos += fraglen; + } + status = count + 2; + +exit: + return status; +} + +/* + * Condition for waking up in dvb_ca_en50221_io_read_condition + */ +static int dvb_ca_en50221_io_read_condition(struct dvb_ca_private *ca, + int *result, int *_slot) +{ + int slot; + int slot_count = 0; + int idx; + size_t fraglen; + int connection_id = -1; + int found = 0; + u8 hdr[2]; + + slot = ca->next_read_slot; + while ((slot_count < ca->slot_count) && (!found)) { + struct dvb_ca_slot *sl = &ca->slot_info[slot]; + + if (sl->slot_state != DVB_CA_SLOTSTATE_RUNNING) + goto nextslot; + + if (!sl->rx_buffer.data) + return 0; + + idx = dvb_ringbuffer_pkt_next(&sl->rx_buffer, -1, &fraglen); + while (idx != -1) { + dvb_ringbuffer_pkt_read(&sl->rx_buffer, idx, 0, hdr, 2); + if (connection_id == -1) + connection_id = hdr[0]; + if ((hdr[0] == connection_id) && + ((hdr[1] & 0x80) == 0)) { + *_slot = slot; + found = 1; + break; + } + + idx = dvb_ringbuffer_pkt_next(&sl->rx_buffer, idx, + &fraglen); + } + +nextslot: + slot = (slot + 1) % ca->slot_count; + slot_count++; + } + + ca->next_read_slot = slot; + return found; +} + +/** + * dvb_ca_en50221_io_read - Implementation of read() syscall. + * + * @file: File structure. + * @buf: Destination buffer. + * @count: Size of destination buffer. + * @ppos: Position in file (ignored). + * + * return: Number of bytes read, or <0 on error. + */ +static ssize_t dvb_ca_en50221_io_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_ca_private *ca = dvbdev->priv; + struct dvb_ca_slot *sl; + int status; + int result = 0; + u8 hdr[2]; + int slot; + int connection_id = -1; + size_t idx, idx2; + int last_fragment = 0; + size_t fraglen; + int pktlen; + int dispose = 0; + + dprintk("%s\n", __func__); + + /* + * Outgoing packet has a 2 byte header. + * hdr[0] = slot_id, hdr[1] = connection_id + */ + if (count < 2) + return -EINVAL; + + /* wait for some data */ + status = dvb_ca_en50221_io_read_condition(ca, &result, &slot); + if (status == 0) { + /* if we're in nonblocking mode, exit immediately */ + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + /* wait for some data */ + status = wait_event_interruptible(ca->wait_queue, + dvb_ca_en50221_io_read_condition + (ca, &result, &slot)); + } + if ((status < 0) || (result < 0)) { + if (result) + return result; + return status; + } + + sl = &ca->slot_info[slot]; + idx = dvb_ringbuffer_pkt_next(&sl->rx_buffer, -1, &fraglen); + pktlen = 2; + do { + if (idx == -1) { + pr_err("dvb_ca adapter %d: BUG: read packet ended before last_fragment encountered\n", + ca->dvbdev->adapter->num); + status = -EIO; + goto exit; + } + + dvb_ringbuffer_pkt_read(&sl->rx_buffer, idx, 0, hdr, 2); + if (connection_id == -1) + connection_id = hdr[0]; + if (hdr[0] == connection_id) { + if (pktlen < count) { + if ((pktlen + fraglen - 2) > count) + fraglen = count - pktlen; + else + fraglen -= 2; + + status = + dvb_ringbuffer_pkt_read_user(&sl->rx_buffer, + idx, 2, + buf + pktlen, + fraglen); + if (status < 0) + goto exit; + + pktlen += fraglen; + } + + if ((hdr[1] & 0x80) == 0) + last_fragment = 1; + dispose = 1; + } + + idx2 = dvb_ringbuffer_pkt_next(&sl->rx_buffer, idx, &fraglen); + if (dispose) + dvb_ringbuffer_pkt_dispose(&sl->rx_buffer, idx); + idx = idx2; + dispose = 0; + } while (!last_fragment); + + hdr[0] = slot; + hdr[1] = connection_id; + status = copy_to_user(buf, hdr, 2); + if (status) { + status = -EFAULT; + goto exit; + } + status = pktlen; + +exit: + return status; +} + +/** + * dvb_ca_en50221_io_open - Implementation of file open syscall. + * + * @inode: Inode concerned. + * @file: File concerned. + * + * return: 0 on success, <0 on failure. + */ +static int dvb_ca_en50221_io_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_ca_private *ca = dvbdev->priv; + int err; + int i; + + dprintk("%s\n", __func__); + + mutex_lock(&ca->remove_mutex); + + if (ca->exit) { + mutex_unlock(&ca->remove_mutex); + return -ENODEV; + } + + if (!try_module_get(ca->pub->owner)) { + mutex_unlock(&ca->remove_mutex); + return -EIO; + } + + err = dvb_generic_open(inode, file); + if (err < 0) { + module_put(ca->pub->owner); + mutex_unlock(&ca->remove_mutex); + return err; + } + + for (i = 0; i < ca->slot_count; i++) { + struct dvb_ca_slot *sl = &ca->slot_info[i]; + + if (sl->slot_state == DVB_CA_SLOTSTATE_RUNNING) { + if (!sl->rx_buffer.data) { + /* + * it is safe to call this here without locks + * because ca->open == 0. Data is not read in + * this case + */ + dvb_ringbuffer_flush(&sl->rx_buffer); + } + } + } + + ca->open = 1; + dvb_ca_en50221_thread_update_delay(ca); + dvb_ca_en50221_thread_wakeup(ca); + + dvb_ca_private_get(ca); + + mutex_unlock(&ca->remove_mutex); + return 0; +} + +/** + * dvb_ca_en50221_io_release - Implementation of file close syscall. + * + * @inode: Inode concerned. + * @file: File concerned. + * + * return: 0 on success, <0 on failure. + */ +static int dvb_ca_en50221_io_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_ca_private *ca = dvbdev->priv; + int err; + + dprintk("%s\n", __func__); + + mutex_lock(&ca->remove_mutex); + + /* mark the CA device as closed */ + ca->open = 0; + dvb_ca_en50221_thread_update_delay(ca); + + err = dvb_generic_release(inode, file); + + module_put(ca->pub->owner); + + dvb_ca_private_put(ca); + + if (dvbdev->users == 1 && ca->exit == 1) { + mutex_unlock(&ca->remove_mutex); + wake_up(&dvbdev->wait_queue); + } else { + mutex_unlock(&ca->remove_mutex); + } + + return err; +} + +/** + * dvb_ca_en50221_io_poll - Implementation of poll() syscall. + * + * @file: File concerned. + * @wait: poll wait table. + * + * return: Standard poll mask. + */ +static __poll_t dvb_ca_en50221_io_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_ca_private *ca = dvbdev->priv; + __poll_t mask = 0; + int slot; + int result = 0; + + dprintk("%s\n", __func__); + + poll_wait(file, &ca->wait_queue, wait); + + if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1) + mask |= EPOLLIN; + + /* if there is something, return now */ + if (mask) + return mask; + + if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1) + mask |= EPOLLIN; + + return mask; +} + +static const struct file_operations dvb_ca_fops = { + .owner = THIS_MODULE, + .read = dvb_ca_en50221_io_read, + .write = dvb_ca_en50221_io_write, + .unlocked_ioctl = dvb_ca_en50221_io_ioctl, + .open = dvb_ca_en50221_io_open, + .release = dvb_ca_en50221_io_release, + .poll = dvb_ca_en50221_io_poll, + .llseek = noop_llseek, +}; + +static const struct dvb_device dvbdev_ca = { + .priv = NULL, + .users = 1, + .readers = 1, + .writers = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-ca-en50221", +#endif + .fops = &dvb_ca_fops, +}; + +/* ************************************************************************** */ +/* Initialisation/shutdown functions */ + +/** + * dvb_ca_en50221_init - Initialise a new DVB CA EN50221 interface device. + * + * @dvb_adapter: DVB adapter to attach the new CA device to. + * @pubca: The dvb_ca instance. + * @flags: Flags describing the CA device (DVB_CA_FLAG_*). + * @slot_count: Number of slots supported. + * + * return: 0 on success, nonzero on failure + */ +int dvb_ca_en50221_init(struct dvb_adapter *dvb_adapter, + struct dvb_ca_en50221 *pubca, int flags, int slot_count) +{ + int ret; + struct dvb_ca_private *ca = NULL; + int i; + + dprintk("%s\n", __func__); + + if (slot_count < 1) + return -EINVAL; + + /* initialise the system data */ + ca = kzalloc(sizeof(*ca), GFP_KERNEL); + if (!ca) { + ret = -ENOMEM; + goto exit; + } + kref_init(&ca->refcount); + ca->pub = pubca; + ca->flags = flags; + ca->slot_count = slot_count; + ca->slot_info = kcalloc(slot_count, sizeof(struct dvb_ca_slot), + GFP_KERNEL); + if (!ca->slot_info) { + ret = -ENOMEM; + goto free_ca; + } + init_waitqueue_head(&ca->wait_queue); + ca->open = 0; + ca->wakeup = 0; + ca->next_read_slot = 0; + pubca->private = ca; + + /* register the DVB device */ + ret = dvb_register_device(dvb_adapter, &ca->dvbdev, &dvbdev_ca, ca, + DVB_DEVICE_CA, 0); + if (ret) + goto free_slot_info; + + /* now initialise each slot */ + for (i = 0; i < slot_count; i++) { + struct dvb_ca_slot *sl = &ca->slot_info[i]; + + memset(sl, 0, sizeof(struct dvb_ca_slot)); + sl->slot_state = DVB_CA_SLOTSTATE_NONE; + atomic_set(&sl->camchange_count, 0); + sl->camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED; + mutex_init(&sl->slot_lock); + } + + mutex_init(&ca->ioctl_mutex); + mutex_init(&ca->remove_mutex); + + if (signal_pending(current)) { + ret = -EINTR; + goto unregister_device; + } + mb(); + + /* create a kthread for monitoring this CA device */ + ca->thread = kthread_run(dvb_ca_en50221_thread, ca, "kdvb-ca-%i:%i", + ca->dvbdev->adapter->num, ca->dvbdev->id); + if (IS_ERR(ca->thread)) { + ret = PTR_ERR(ca->thread); + pr_err("dvb_ca_init: failed to start kernel_thread (%d)\n", + ret); + goto unregister_device; + } + return 0; + +unregister_device: + dvb_unregister_device(ca->dvbdev); +free_slot_info: + kfree(ca->slot_info); +free_ca: + kfree(ca); +exit: + pubca->private = NULL; + return ret; +} +EXPORT_SYMBOL(dvb_ca_en50221_init); + +/** + * dvb_ca_en50221_release - Release a DVB CA EN50221 interface device. + * + * @pubca: The associated dvb_ca instance. + */ +void dvb_ca_en50221_release(struct dvb_ca_en50221 *pubca) +{ + struct dvb_ca_private *ca = pubca->private; + int i; + + dprintk("%s\n", __func__); + + mutex_lock(&ca->remove_mutex); + ca->exit = 1; + mutex_unlock(&ca->remove_mutex); + + if (ca->dvbdev->users < 1) + wait_event(ca->dvbdev->wait_queue, + ca->dvbdev->users == 1); + + /* shutdown the thread if there was one */ + kthread_stop(ca->thread); + + for (i = 0; i < ca->slot_count; i++) + dvb_ca_en50221_slot_shutdown(ca, i); + + dvb_remove_device(ca->dvbdev); + dvb_ca_private_put(ca); + pubca->private = NULL; +} +EXPORT_SYMBOL(dvb_ca_en50221_release); diff --git a/drivers/media/dvb-core/dvb_demux.c b/drivers/media/dvb-core/dvb_demux.c new file mode 100644 index 000000000..eadd579bb --- /dev/null +++ b/drivers/media/dvb-core/dvb_demux.c @@ -0,0 +1,1318 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * dvb_demux.c - DVB kernel demux API + * + * Copyright (C) 2000-2001 Ralph Metzler <ralph@convergence.de> + * & Marcus Metzler <marcus@convergence.de> + * for convergence integrated media GmbH + */ + +#define pr_fmt(fmt) "dvb_demux: " fmt + +#include <linux/sched/signal.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/string.h> +#include <linux/crc32.h> +#include <linux/uaccess.h> +#include <asm/div64.h> + +#include <media/dvb_demux.h> + +static int dvb_demux_tscheck; +module_param(dvb_demux_tscheck, int, 0644); +MODULE_PARM_DESC(dvb_demux_tscheck, + "enable transport stream continuity and TEI check"); + +static int dvb_demux_speedcheck; +module_param(dvb_demux_speedcheck, int, 0644); +MODULE_PARM_DESC(dvb_demux_speedcheck, + "enable transport stream speed check"); + +static int dvb_demux_feed_err_pkts = 1; +module_param(dvb_demux_feed_err_pkts, int, 0644); +MODULE_PARM_DESC(dvb_demux_feed_err_pkts, + "when set to 0, drop packets with the TEI bit set (1 by default)"); + +#define dprintk(fmt, arg...) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), __func__, ##arg) + +#define dprintk_tscheck(x...) do { \ + if (dvb_demux_tscheck && printk_ratelimit()) \ + dprintk(x); \ +} while (0) + +#ifdef CONFIG_DVB_DEMUX_SECTION_LOSS_LOG +# define dprintk_sect_loss(x...) dprintk(x) +#else +# define dprintk_sect_loss(x...) +#endif + +#define set_buf_flags(__feed, __flag) \ + do { \ + (__feed)->buffer_flags |= (__flag); \ + } while (0) + +/****************************************************************************** + * static inlined helper functions + ******************************************************************************/ + +static inline u16 section_length(const u8 *buf) +{ + return 3 + ((buf[1] & 0x0f) << 8) + buf[2]; +} + +static inline u16 ts_pid(const u8 *buf) +{ + return ((buf[1] & 0x1f) << 8) + buf[2]; +} + +static inline u8 payload(const u8 *tsp) +{ + if (!(tsp[3] & 0x10)) // no payload? + return 0; + + if (tsp[3] & 0x20) { // adaptation field? + if (tsp[4] > 183) // corrupted data? + return 0; + else + return 184 - 1 - tsp[4]; + } + + return 184; +} + +static u32 dvb_dmx_crc32(struct dvb_demux_feed *f, const u8 *src, size_t len) +{ + return (f->feed.sec.crc_val = crc32_be(f->feed.sec.crc_val, src, len)); +} + +static void dvb_dmx_memcopy(struct dvb_demux_feed *f, u8 *d, const u8 *s, + size_t len) +{ + memcpy(d, s, len); +} + +/****************************************************************************** + * Software filter functions + ******************************************************************************/ + +static inline int dvb_dmx_swfilter_payload(struct dvb_demux_feed *feed, + const u8 *buf) +{ + int count = payload(buf); + int p; + int ccok; + u8 cc; + + if (count == 0) + return -1; + + p = 188 - count; + + cc = buf[3] & 0x0f; + ccok = ((feed->cc + 1) & 0x0f) == cc; + if (!ccok) { + set_buf_flags(feed, DMX_BUFFER_FLAG_DISCONTINUITY_DETECTED); + dprintk_sect_loss("missed packet: %d instead of %d!\n", + cc, (feed->cc + 1) & 0x0f); + } + feed->cc = cc; + + if (buf[1] & 0x40) // PUSI ? + feed->peslen = 0xfffa; + + feed->peslen += count; + + return feed->cb.ts(&buf[p], count, NULL, 0, &feed->feed.ts, + &feed->buffer_flags); +} + +static int dvb_dmx_swfilter_sectionfilter(struct dvb_demux_feed *feed, + struct dvb_demux_filter *f) +{ + u8 neq = 0; + int i; + + for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { + u8 xor = f->filter.filter_value[i] ^ feed->feed.sec.secbuf[i]; + + if (f->maskandmode[i] & xor) + return 0; + + neq |= f->maskandnotmode[i] & xor; + } + + if (f->doneq && !neq) + return 0; + + return feed->cb.sec(feed->feed.sec.secbuf, feed->feed.sec.seclen, + NULL, 0, &f->filter, &feed->buffer_flags); +} + +static inline int dvb_dmx_swfilter_section_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct dvb_demux_filter *f = feed->filter; + struct dmx_section_feed *sec = &feed->feed.sec; + int section_syntax_indicator; + + if (!sec->is_filtering) + return 0; + + if (!f) + return 0; + + if (sec->check_crc) { + section_syntax_indicator = ((sec->secbuf[1] & 0x80) != 0); + if (section_syntax_indicator && + demux->check_crc32(feed, sec->secbuf, sec->seclen)) { + set_buf_flags(feed, DMX_BUFFER_FLAG_HAD_CRC32_DISCARD); + return -1; + } + } + + do { + if (dvb_dmx_swfilter_sectionfilter(feed, f) < 0) + return -1; + } while ((f = f->next) && sec->is_filtering); + + sec->seclen = 0; + + return 0; +} + +static void dvb_dmx_swfilter_section_new(struct dvb_demux_feed *feed) +{ + struct dmx_section_feed *sec = &feed->feed.sec; + + if (sec->secbufp < sec->tsfeedp) { + int n = sec->tsfeedp - sec->secbufp; + + /* + * Section padding is done with 0xff bytes entirely. + * Due to speed reasons, we won't check all of them + * but just first and last. + */ + if (sec->secbuf[0] != 0xff || sec->secbuf[n - 1] != 0xff) { + set_buf_flags(feed, + DMX_BUFFER_FLAG_DISCONTINUITY_DETECTED); + dprintk_sect_loss("section ts padding loss: %d/%d\n", + n, sec->tsfeedp); + dprintk_sect_loss("pad data: %*ph\n", n, sec->secbuf); + } + } + + sec->tsfeedp = sec->secbufp = sec->seclen = 0; + sec->secbuf = sec->secbuf_base; +} + +/* + * Losless Section Demux 1.4.1 by Emard + * Valsecchi Patrick: + * - middle of section A (no PUSI) + * - end of section A and start of section B + * (with PUSI pointing to the start of the second section) + * + * In this case, without feed->pusi_seen you'll receive a garbage section + * consisting of the end of section A. Basically because tsfeedp + * is incemented and the use=0 condition is not raised + * when the second packet arrives. + * + * Fix: + * when demux is started, let feed->pusi_seen = false to + * prevent initial feeding of garbage from the end of + * previous section. When you for the first time see PUSI=1 + * then set feed->pusi_seen = true + */ +static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, + const u8 *buf, u8 len) +{ + struct dvb_demux *demux = feed->demux; + struct dmx_section_feed *sec = &feed->feed.sec; + u16 limit, seclen, n; + + if (sec->tsfeedp >= DMX_MAX_SECFEED_SIZE) + return 0; + + if (sec->tsfeedp + len > DMX_MAX_SECFEED_SIZE) { + set_buf_flags(feed, DMX_BUFFER_FLAG_DISCONTINUITY_DETECTED); + dprintk_sect_loss("section buffer full loss: %d/%d\n", + sec->tsfeedp + len - DMX_MAX_SECFEED_SIZE, + DMX_MAX_SECFEED_SIZE); + len = DMX_MAX_SECFEED_SIZE - sec->tsfeedp; + } + + if (len <= 0) + return 0; + + demux->memcopy(feed, sec->secbuf_base + sec->tsfeedp, buf, len); + sec->tsfeedp += len; + + /* + * Dump all the sections we can find in the data (Emard) + */ + limit = sec->tsfeedp; + if (limit > DMX_MAX_SECFEED_SIZE) + return -1; /* internal error should never happen */ + + /* to be sure always set secbuf */ + sec->secbuf = sec->secbuf_base + sec->secbufp; + + for (n = 0; sec->secbufp + 2 < limit; n++) { + seclen = section_length(sec->secbuf); + if (seclen <= 0 || seclen > DMX_MAX_SECTION_SIZE + || seclen + sec->secbufp > limit) + return 0; + sec->seclen = seclen; + sec->crc_val = ~0; + /* dump [secbuf .. secbuf+seclen) */ + if (feed->pusi_seen) { + dvb_dmx_swfilter_section_feed(feed); + } else { + set_buf_flags(feed, + DMX_BUFFER_FLAG_DISCONTINUITY_DETECTED); + dprintk_sect_loss("pusi not seen, discarding section data\n"); + } + sec->secbufp += seclen; /* secbufp and secbuf moving together is */ + sec->secbuf += seclen; /* redundant but saves pointer arithmetic */ + } + + return 0; +} + +static int dvb_dmx_swfilter_section_packet(struct dvb_demux_feed *feed, + const u8 *buf) +{ + u8 p, count; + int ccok, dc_i = 0; + u8 cc; + + count = payload(buf); + + if (count == 0) /* count == 0 if no payload or out of range */ + return -1; + + p = 188 - count; /* payload start */ + + cc = buf[3] & 0x0f; + ccok = ((feed->cc + 1) & 0x0f) == cc; + + if (buf[3] & 0x20) { + /* adaption field present, check for discontinuity_indicator */ + if ((buf[4] > 0) && (buf[5] & 0x80)) + dc_i = 1; + } + + if (!ccok || dc_i) { + if (dc_i) { + set_buf_flags(feed, + DMX_BUFFER_FLAG_DISCONTINUITY_INDICATOR); + dprintk_sect_loss("%d frame with disconnect indicator\n", + cc); + } else { + set_buf_flags(feed, + DMX_BUFFER_FLAG_DISCONTINUITY_DETECTED); + dprintk_sect_loss("discontinuity: %d instead of %d. %d bytes lost\n", + cc, (feed->cc + 1) & 0x0f, count + 4); + } + /* + * those bytes under some circumstances will again be reported + * in the following dvb_dmx_swfilter_section_new + */ + + /* + * Discontinuity detected. Reset pusi_seen to + * stop feeding of suspicious data until next PUSI=1 arrives + * + * FIXME: does it make sense if the MPEG-TS is the one + * reporting discontinuity? + */ + + feed->pusi_seen = false; + dvb_dmx_swfilter_section_new(feed); + } + feed->cc = cc; + + if (buf[1] & 0x40) { + /* PUSI=1 (is set), section boundary is here */ + if (count > 1 && buf[p] < count) { + const u8 *before = &buf[p + 1]; + u8 before_len = buf[p]; + const u8 *after = &before[before_len]; + u8 after_len = count - 1 - before_len; + + dvb_dmx_swfilter_section_copy_dump(feed, before, + before_len); + /* before start of new section, set pusi_seen */ + feed->pusi_seen = true; + dvb_dmx_swfilter_section_new(feed); + dvb_dmx_swfilter_section_copy_dump(feed, after, + after_len); + } else if (count > 0) { + set_buf_flags(feed, + DMX_BUFFER_FLAG_DISCONTINUITY_DETECTED); + dprintk_sect_loss("PUSI=1 but %d bytes lost\n", count); + } + } else { + /* PUSI=0 (is not set), no section boundary */ + dvb_dmx_swfilter_section_copy_dump(feed, &buf[p], count); + } + + return 0; +} + +static inline void dvb_dmx_swfilter_packet_type(struct dvb_demux_feed *feed, + const u8 *buf) +{ + switch (feed->type) { + case DMX_TYPE_TS: + if (!feed->feed.ts.is_filtering) + break; + if (feed->ts_type & TS_PACKET) { + if (feed->ts_type & TS_PAYLOAD_ONLY) + dvb_dmx_swfilter_payload(feed, buf); + else + feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, + &feed->buffer_flags); + } + /* Used only on full-featured devices */ + if (feed->ts_type & TS_DECODER) + if (feed->demux->write_to_decoder) + feed->demux->write_to_decoder(feed, buf, 188); + break; + + case DMX_TYPE_SEC: + if (!feed->feed.sec.is_filtering) + break; + if (dvb_dmx_swfilter_section_packet(feed, buf) < 0) + feed->feed.sec.seclen = feed->feed.sec.secbufp = 0; + break; + + default: + break; + } +} + +#define DVR_FEED(f) \ + (((f)->type == DMX_TYPE_TS) && \ + ((f)->feed.ts.is_filtering) && \ + (((f)->ts_type & (TS_PACKET | TS_DEMUX)) == TS_PACKET)) + +static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) +{ + struct dvb_demux_feed *feed; + u16 pid = ts_pid(buf); + int dvr_done = 0; + + if (dvb_demux_speedcheck) { + ktime_t cur_time; + u64 speed_bytes, speed_timedelta; + + demux->speed_pkts_cnt++; + + /* show speed every SPEED_PKTS_INTERVAL packets */ + if (!(demux->speed_pkts_cnt % SPEED_PKTS_INTERVAL)) { + cur_time = ktime_get(); + + if (ktime_to_ns(demux->speed_last_time) != 0) { + speed_bytes = (u64)demux->speed_pkts_cnt + * 188 * 8; + /* convert to 1024 basis */ + speed_bytes = 1000 * div64_u64(speed_bytes, + 1024); + speed_timedelta = ktime_ms_delta(cur_time, + demux->speed_last_time); + if (speed_timedelta) + dprintk("TS speed %llu Kbits/sec \n", + div64_u64(speed_bytes, + speed_timedelta)); + } + + demux->speed_last_time = cur_time; + demux->speed_pkts_cnt = 0; + } + } + + if (buf[1] & 0x80) { + list_for_each_entry(feed, &demux->feed_list, list_head) { + if ((feed->pid != pid) && (feed->pid != 0x2000)) + continue; + set_buf_flags(feed, DMX_BUFFER_FLAG_TEI); + } + dprintk_tscheck("TEI detected. PID=0x%x data1=0x%x\n", + pid, buf[1]); + /* data in this packet can't be trusted - drop it unless + * module option dvb_demux_feed_err_pkts is set */ + if (!dvb_demux_feed_err_pkts) + return; + } else /* if TEI bit is set, pid may be wrong- skip pkt counter */ + if (demux->cnt_storage && dvb_demux_tscheck) { + /* check pkt counter */ + if (pid < MAX_PID) { + if (buf[3] & 0x10) + demux->cnt_storage[pid] = + (demux->cnt_storage[pid] + 1) & 0xf; + + if ((buf[3] & 0xf) != demux->cnt_storage[pid]) { + list_for_each_entry(feed, &demux->feed_list, list_head) { + if ((feed->pid != pid) && (feed->pid != 0x2000)) + continue; + set_buf_flags(feed, + DMX_BUFFER_PKT_COUNTER_MISMATCH); + } + + dprintk_tscheck("TS packet counter mismatch. PID=0x%x expected 0x%x got 0x%x\n", + pid, demux->cnt_storage[pid], + buf[3] & 0xf); + demux->cnt_storage[pid] = buf[3] & 0xf; + } + } + /* end check */ + } + + list_for_each_entry(feed, &demux->feed_list, list_head) { + if ((feed->pid != pid) && (feed->pid != 0x2000)) + continue; + + /* copy each packet only once to the dvr device, even + * if a PID is in multiple filters (e.g. video + PCR) */ + if ((DVR_FEED(feed)) && (dvr_done++)) + continue; + + if (feed->pid == pid) + dvb_dmx_swfilter_packet_type(feed, buf); + else if (feed->pid == 0x2000) + feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, + &feed->buffer_flags); + } +} + +void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, + size_t count) +{ + unsigned long flags; + + spin_lock_irqsave(&demux->lock, flags); + + while (count--) { + if (buf[0] == 0x47) + dvb_dmx_swfilter_packet(demux, buf); + buf += 188; + } + + spin_unlock_irqrestore(&demux->lock, flags); +} + +EXPORT_SYMBOL(dvb_dmx_swfilter_packets); + +static inline int find_next_packet(const u8 *buf, int pos, size_t count, + const int pktsize) +{ + int start = pos, lost; + + while (pos < count) { + if (buf[pos] == 0x47 || + (pktsize == 204 && buf[pos] == 0xB8)) + break; + pos++; + } + + lost = pos - start; + if (lost) { + /* This garbage is part of a valid packet? */ + int backtrack = pos - pktsize; + if (backtrack >= 0 && (buf[backtrack] == 0x47 || + (pktsize == 204 && buf[backtrack] == 0xB8))) + return backtrack; + } + + return pos; +} + +/* Filter all pktsize= 188 or 204 sized packets and skip garbage. */ +static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, + size_t count, const int pktsize) +{ + int p = 0, i, j; + const u8 *q; + unsigned long flags; + + spin_lock_irqsave(&demux->lock, flags); + + if (demux->tsbufp) { /* tsbuf[0] is now 0x47. */ + i = demux->tsbufp; + j = pktsize - i; + if (count < j) { + memcpy(&demux->tsbuf[i], buf, count); + demux->tsbufp += count; + goto bailout; + } + memcpy(&demux->tsbuf[i], buf, j); + if (demux->tsbuf[0] == 0x47) /* double check */ + dvb_dmx_swfilter_packet(demux, demux->tsbuf); + demux->tsbufp = 0; + p += j; + } + + while (1) { + p = find_next_packet(buf, p, count, pktsize); + if (p >= count) + break; + if (count - p < pktsize) + break; + + q = &buf[p]; + + if (pktsize == 204 && (*q == 0xB8)) { + memcpy(demux->tsbuf, q, 188); + demux->tsbuf[0] = 0x47; + q = demux->tsbuf; + } + dvb_dmx_swfilter_packet(demux, q); + p += pktsize; + } + + i = count - p; + if (i) { + memcpy(demux->tsbuf, &buf[p], i); + demux->tsbufp = i; + if (pktsize == 204 && demux->tsbuf[0] == 0xB8) + demux->tsbuf[0] = 0x47; + } + +bailout: + spin_unlock_irqrestore(&demux->lock, flags); +} + +void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + _dvb_dmx_swfilter(demux, buf, count, 188); +} +EXPORT_SYMBOL(dvb_dmx_swfilter); + +void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + _dvb_dmx_swfilter(demux, buf, count, 204); +} +EXPORT_SYMBOL(dvb_dmx_swfilter_204); + +void dvb_dmx_swfilter_raw(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + unsigned long flags; + + spin_lock_irqsave(&demux->lock, flags); + + demux->feed->cb.ts(buf, count, NULL, 0, &demux->feed->feed.ts, + &demux->feed->buffer_flags); + + spin_unlock_irqrestore(&demux->lock, flags); +} +EXPORT_SYMBOL(dvb_dmx_swfilter_raw); + +static struct dvb_demux_filter *dvb_dmx_filter_alloc(struct dvb_demux *demux) +{ + int i; + + for (i = 0; i < demux->filternum; i++) + if (demux->filter[i].state == DMX_STATE_FREE) + break; + + if (i == demux->filternum) + return NULL; + + demux->filter[i].state = DMX_STATE_ALLOCATED; + + return &demux->filter[i]; +} + +static struct dvb_demux_feed *dvb_dmx_feed_alloc(struct dvb_demux *demux) +{ + int i; + + for (i = 0; i < demux->feednum; i++) + if (demux->feed[i].state == DMX_STATE_FREE) + break; + + if (i == demux->feednum) + return NULL; + + demux->feed[i].state = DMX_STATE_ALLOCATED; + + return &demux->feed[i]; +} + +static int dvb_demux_feed_find(struct dvb_demux_feed *feed) +{ + struct dvb_demux_feed *entry; + + list_for_each_entry(entry, &feed->demux->feed_list, list_head) + if (entry == feed) + return 1; + + return 0; +} + +static void dvb_demux_feed_add(struct dvb_demux_feed *feed) +{ + spin_lock_irq(&feed->demux->lock); + if (dvb_demux_feed_find(feed)) { + pr_err("%s: feed already in list (type=%x state=%x pid=%x)\n", + __func__, feed->type, feed->state, feed->pid); + goto out; + } + + list_add(&feed->list_head, &feed->demux->feed_list); +out: + spin_unlock_irq(&feed->demux->lock); +} + +static void dvb_demux_feed_del(struct dvb_demux_feed *feed) +{ + spin_lock_irq(&feed->demux->lock); + if (!(dvb_demux_feed_find(feed))) { + pr_err("%s: feed not in list (type=%x state=%x pid=%x)\n", + __func__, feed->type, feed->state, feed->pid); + goto out; + } + + list_del(&feed->list_head); +out: + spin_unlock_irq(&feed->demux->lock); +} + +static int dmx_ts_feed_set(struct dmx_ts_feed *ts_feed, u16 pid, int ts_type, + enum dmx_ts_pes pes_type, ktime_t timeout) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + struct dvb_demux *demux = feed->demux; + + if (pid > DMX_MAX_PID) + return -EINVAL; + + if (mutex_lock_interruptible(&demux->mutex)) + return -ERESTARTSYS; + + if (ts_type & TS_DECODER) { + if (pes_type >= DMX_PES_OTHER) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + if (demux->pesfilter[pes_type] && + demux->pesfilter[pes_type] != feed) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + demux->pesfilter[pes_type] = feed; + demux->pids[pes_type] = pid; + } + + dvb_demux_feed_add(feed); + + feed->pid = pid; + feed->timeout = timeout; + feed->ts_type = ts_type; + feed->pes_type = pes_type; + + feed->state = DMX_STATE_READY; + mutex_unlock(&demux->mutex); + + return 0; +} + +static int dmx_ts_feed_start_filtering(struct dmx_ts_feed *ts_feed) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + struct dvb_demux *demux = feed->demux; + int ret; + + if (mutex_lock_interruptible(&demux->mutex)) + return -ERESTARTSYS; + + if (feed->state != DMX_STATE_READY || feed->type != DMX_TYPE_TS) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + if (!demux->start_feed) { + mutex_unlock(&demux->mutex); + return -ENODEV; + } + + if ((ret = demux->start_feed(feed)) < 0) { + mutex_unlock(&demux->mutex); + return ret; + } + + spin_lock_irq(&demux->lock); + ts_feed->is_filtering = 1; + feed->state = DMX_STATE_GO; + spin_unlock_irq(&demux->lock); + mutex_unlock(&demux->mutex); + + return 0; +} + +static int dmx_ts_feed_stop_filtering(struct dmx_ts_feed *ts_feed) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + struct dvb_demux *demux = feed->demux; + int ret; + + mutex_lock(&demux->mutex); + + if (feed->state < DMX_STATE_GO) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + if (!demux->stop_feed) { + mutex_unlock(&demux->mutex); + return -ENODEV; + } + + ret = demux->stop_feed(feed); + + spin_lock_irq(&demux->lock); + ts_feed->is_filtering = 0; + feed->state = DMX_STATE_ALLOCATED; + spin_unlock_irq(&demux->lock); + mutex_unlock(&demux->mutex); + + return ret; +} + +static int dvbdmx_allocate_ts_feed(struct dmx_demux *dmx, + struct dmx_ts_feed **ts_feed, + dmx_ts_cb callback) +{ + struct dvb_demux *demux = (struct dvb_demux *)dmx; + struct dvb_demux_feed *feed; + + if (mutex_lock_interruptible(&demux->mutex)) + return -ERESTARTSYS; + + if (!(feed = dvb_dmx_feed_alloc(demux))) { + mutex_unlock(&demux->mutex); + return -EBUSY; + } + + feed->type = DMX_TYPE_TS; + feed->cb.ts = callback; + feed->demux = demux; + feed->pid = 0xffff; + feed->peslen = 0xfffa; + feed->buffer_flags = 0; + + (*ts_feed) = &feed->feed.ts; + (*ts_feed)->parent = dmx; + (*ts_feed)->priv = NULL; + (*ts_feed)->is_filtering = 0; + (*ts_feed)->start_filtering = dmx_ts_feed_start_filtering; + (*ts_feed)->stop_filtering = dmx_ts_feed_stop_filtering; + (*ts_feed)->set = dmx_ts_feed_set; + + if (!(feed->filter = dvb_dmx_filter_alloc(demux))) { + feed->state = DMX_STATE_FREE; + mutex_unlock(&demux->mutex); + return -EBUSY; + } + + feed->filter->type = DMX_TYPE_TS; + feed->filter->feed = feed; + feed->filter->state = DMX_STATE_READY; + + mutex_unlock(&demux->mutex); + + return 0; +} + +static int dvbdmx_release_ts_feed(struct dmx_demux *dmx, + struct dmx_ts_feed *ts_feed) +{ + struct dvb_demux *demux = (struct dvb_demux *)dmx; + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + + mutex_lock(&demux->mutex); + + if (feed->state == DMX_STATE_FREE) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + feed->state = DMX_STATE_FREE; + feed->filter->state = DMX_STATE_FREE; + + dvb_demux_feed_del(feed); + + feed->pid = 0xffff; + + if (feed->ts_type & TS_DECODER && feed->pes_type < DMX_PES_OTHER) + demux->pesfilter[feed->pes_type] = NULL; + + mutex_unlock(&demux->mutex); + return 0; +} + +/****************************************************************************** + * dmx_section_feed API calls + ******************************************************************************/ + +static int dmx_section_feed_allocate_filter(struct dmx_section_feed *feed, + struct dmx_section_filter **filter) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdemux = dvbdmxfeed->demux; + struct dvb_demux_filter *dvbdmxfilter; + + if (mutex_lock_interruptible(&dvbdemux->mutex)) + return -ERESTARTSYS; + + dvbdmxfilter = dvb_dmx_filter_alloc(dvbdemux); + if (!dvbdmxfilter) { + mutex_unlock(&dvbdemux->mutex); + return -EBUSY; + } + + spin_lock_irq(&dvbdemux->lock); + *filter = &dvbdmxfilter->filter; + (*filter)->parent = feed; + (*filter)->priv = NULL; + dvbdmxfilter->feed = dvbdmxfeed; + dvbdmxfilter->type = DMX_TYPE_SEC; + dvbdmxfilter->state = DMX_STATE_READY; + dvbdmxfilter->next = dvbdmxfeed->filter; + dvbdmxfeed->filter = dvbdmxfilter; + spin_unlock_irq(&dvbdemux->lock); + + mutex_unlock(&dvbdemux->mutex); + return 0; +} + +static int dmx_section_feed_set(struct dmx_section_feed *feed, + u16 pid, int check_crc) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + if (pid > 0x1fff) + return -EINVAL; + + if (mutex_lock_interruptible(&dvbdmx->mutex)) + return -ERESTARTSYS; + + dvb_demux_feed_add(dvbdmxfeed); + + dvbdmxfeed->pid = pid; + dvbdmxfeed->feed.sec.check_crc = check_crc; + + dvbdmxfeed->state = DMX_STATE_READY; + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static void prepare_secfilters(struct dvb_demux_feed *dvbdmxfeed) +{ + int i; + struct dvb_demux_filter *f; + struct dmx_section_filter *sf; + u8 mask, mode, doneq; + + if (!(f = dvbdmxfeed->filter)) + return; + do { + sf = &f->filter; + doneq = false; + for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { + mode = sf->filter_mode[i]; + mask = sf->filter_mask[i]; + f->maskandmode[i] = mask & mode; + doneq |= f->maskandnotmode[i] = mask & ~mode; + } + f->doneq = doneq ? true : false; + } while ((f = f->next)); +} + +static int dmx_section_feed_start_filtering(struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + int ret; + + if (mutex_lock_interruptible(&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (feed->is_filtering) { + mutex_unlock(&dvbdmx->mutex); + return -EBUSY; + } + + if (!dvbdmxfeed->filter) { + mutex_unlock(&dvbdmx->mutex); + return -EINVAL; + } + + dvbdmxfeed->feed.sec.tsfeedp = 0; + dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; + dvbdmxfeed->feed.sec.secbufp = 0; + dvbdmxfeed->feed.sec.seclen = 0; + dvbdmxfeed->pusi_seen = false; + + if (!dvbdmx->start_feed) { + mutex_unlock(&dvbdmx->mutex); + return -ENODEV; + } + + prepare_secfilters(dvbdmxfeed); + + if ((ret = dvbdmx->start_feed(dvbdmxfeed)) < 0) { + mutex_unlock(&dvbdmx->mutex); + return ret; + } + + spin_lock_irq(&dvbdmx->lock); + feed->is_filtering = 1; + dvbdmxfeed->state = DMX_STATE_GO; + spin_unlock_irq(&dvbdmx->lock); + + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static int dmx_section_feed_stop_filtering(struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + int ret; + + mutex_lock(&dvbdmx->mutex); + + if (!dvbdmx->stop_feed) { + mutex_unlock(&dvbdmx->mutex); + return -ENODEV; + } + + ret = dvbdmx->stop_feed(dvbdmxfeed); + + spin_lock_irq(&dvbdmx->lock); + dvbdmxfeed->state = DMX_STATE_READY; + feed->is_filtering = 0; + spin_unlock_irq(&dvbdmx->lock); + + mutex_unlock(&dvbdmx->mutex); + return ret; +} + +static int dmx_section_feed_release_filter(struct dmx_section_feed *feed, + struct dmx_section_filter *filter) +{ + struct dvb_demux_filter *dvbdmxfilter = (struct dvb_demux_filter *)filter, *f; + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + mutex_lock(&dvbdmx->mutex); + + if (dvbdmxfilter->feed != dvbdmxfeed) { + mutex_unlock(&dvbdmx->mutex); + return -EINVAL; + } + + if (feed->is_filtering) { + /* release dvbdmx->mutex as far as it is + acquired by stop_filtering() itself */ + mutex_unlock(&dvbdmx->mutex); + feed->stop_filtering(feed); + mutex_lock(&dvbdmx->mutex); + } + + spin_lock_irq(&dvbdmx->lock); + f = dvbdmxfeed->filter; + + if (f == dvbdmxfilter) { + dvbdmxfeed->filter = dvbdmxfilter->next; + } else { + while (f->next != dvbdmxfilter) + f = f->next; + f->next = f->next->next; + } + + dvbdmxfilter->state = DMX_STATE_FREE; + spin_unlock_irq(&dvbdmx->lock); + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static int dvbdmx_allocate_section_feed(struct dmx_demux *demux, + struct dmx_section_feed **feed, + dmx_section_cb callback) +{ + struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; + struct dvb_demux_feed *dvbdmxfeed; + + if (mutex_lock_interruptible(&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (!(dvbdmxfeed = dvb_dmx_feed_alloc(dvbdmx))) { + mutex_unlock(&dvbdmx->mutex); + return -EBUSY; + } + + dvbdmxfeed->type = DMX_TYPE_SEC; + dvbdmxfeed->cb.sec = callback; + dvbdmxfeed->demux = dvbdmx; + dvbdmxfeed->pid = 0xffff; + dvbdmxfeed->buffer_flags = 0; + dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; + dvbdmxfeed->feed.sec.secbufp = dvbdmxfeed->feed.sec.seclen = 0; + dvbdmxfeed->feed.sec.tsfeedp = 0; + dvbdmxfeed->filter = NULL; + + (*feed) = &dvbdmxfeed->feed.sec; + (*feed)->is_filtering = 0; + (*feed)->parent = demux; + (*feed)->priv = NULL; + + (*feed)->set = dmx_section_feed_set; + (*feed)->allocate_filter = dmx_section_feed_allocate_filter; + (*feed)->start_filtering = dmx_section_feed_start_filtering; + (*feed)->stop_filtering = dmx_section_feed_stop_filtering; + (*feed)->release_filter = dmx_section_feed_release_filter; + + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static int dvbdmx_release_section_feed(struct dmx_demux *demux, + struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; + + mutex_lock(&dvbdmx->mutex); + + if (dvbdmxfeed->state == DMX_STATE_FREE) { + mutex_unlock(&dvbdmx->mutex); + return -EINVAL; + } + dvbdmxfeed->state = DMX_STATE_FREE; + + dvb_demux_feed_del(dvbdmxfeed); + + dvbdmxfeed->pid = 0xffff; + + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +/****************************************************************************** + * dvb_demux kernel data API calls + ******************************************************************************/ + +static int dvbdmx_open(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (dvbdemux->users >= MAX_DVB_DEMUX_USERS) + return -EUSERS; + + dvbdemux->users++; + return 0; +} + +static int dvbdmx_close(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (dvbdemux->users == 0) + return -ENODEV; + + dvbdemux->users--; + //FIXME: release any unneeded resources if users==0 + return 0; +} + +static int dvbdmx_write(struct dmx_demux *demux, const char __user *buf, size_t count) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + void *p; + + if ((!demux->frontend) || (demux->frontend->source != DMX_MEMORY_FE)) + return -EINVAL; + + p = memdup_user(buf, count); + if (IS_ERR(p)) + return PTR_ERR(p); + if (mutex_lock_interruptible(&dvbdemux->mutex)) { + kfree(p); + return -ERESTARTSYS; + } + dvb_dmx_swfilter(dvbdemux, p, count); + kfree(p); + mutex_unlock(&dvbdemux->mutex); + + if (signal_pending(current)) + return -EINTR; + return count; +} + +static int dvbdmx_add_frontend(struct dmx_demux *demux, + struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + struct list_head *head = &dvbdemux->frontend_list; + + list_add(&(frontend->connectivity_list), head); + + return 0; +} + +static int dvbdmx_remove_frontend(struct dmx_demux *demux, + struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + struct list_head *pos, *n, *head = &dvbdemux->frontend_list; + + list_for_each_safe(pos, n, head) { + if (DMX_FE_ENTRY(pos) == frontend) { + list_del(pos); + return 0; + } + } + + return -ENODEV; +} + +static struct list_head *dvbdmx_get_frontends(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (list_empty(&dvbdemux->frontend_list)) + return NULL; + + return &dvbdemux->frontend_list; +} + +static int dvbdmx_connect_frontend(struct dmx_demux *demux, + struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (demux->frontend) + return -EINVAL; + + mutex_lock(&dvbdemux->mutex); + + demux->frontend = frontend; + mutex_unlock(&dvbdemux->mutex); + return 0; +} + +static int dvbdmx_disconnect_frontend(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + mutex_lock(&dvbdemux->mutex); + + demux->frontend = NULL; + mutex_unlock(&dvbdemux->mutex); + return 0; +} + +static int dvbdmx_get_pes_pids(struct dmx_demux *demux, u16 * pids) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + memcpy(pids, dvbdemux->pids, 5 * sizeof(u16)); + return 0; +} + +int dvb_dmx_init(struct dvb_demux *dvbdemux) +{ + int i; + struct dmx_demux *dmx = &dvbdemux->dmx; + + dvbdemux->cnt_storage = NULL; + dvbdemux->users = 0; + dvbdemux->filter = vmalloc(array_size(sizeof(struct dvb_demux_filter), + dvbdemux->filternum)); + + if (!dvbdemux->filter) + return -ENOMEM; + + dvbdemux->feed = vmalloc(array_size(sizeof(struct dvb_demux_feed), + dvbdemux->feednum)); + if (!dvbdemux->feed) { + vfree(dvbdemux->filter); + dvbdemux->filter = NULL; + return -ENOMEM; + } + for (i = 0; i < dvbdemux->filternum; i++) { + dvbdemux->filter[i].state = DMX_STATE_FREE; + dvbdemux->filter[i].index = i; + } + for (i = 0; i < dvbdemux->feednum; i++) { + dvbdemux->feed[i].state = DMX_STATE_FREE; + dvbdemux->feed[i].index = i; + } + + dvbdemux->cnt_storage = vmalloc(MAX_PID + 1); + if (!dvbdemux->cnt_storage) + pr_warn("Couldn't allocate memory for TS/TEI check. Disabling it\n"); + + INIT_LIST_HEAD(&dvbdemux->frontend_list); + + for (i = 0; i < DMX_PES_OTHER; i++) { + dvbdemux->pesfilter[i] = NULL; + dvbdemux->pids[i] = 0xffff; + } + + INIT_LIST_HEAD(&dvbdemux->feed_list); + + dvbdemux->playing = 0; + dvbdemux->recording = 0; + dvbdemux->tsbufp = 0; + + if (!dvbdemux->check_crc32) + dvbdemux->check_crc32 = dvb_dmx_crc32; + + if (!dvbdemux->memcopy) + dvbdemux->memcopy = dvb_dmx_memcopy; + + dmx->frontend = NULL; + dmx->priv = dvbdemux; + dmx->open = dvbdmx_open; + dmx->close = dvbdmx_close; + dmx->write = dvbdmx_write; + dmx->allocate_ts_feed = dvbdmx_allocate_ts_feed; + dmx->release_ts_feed = dvbdmx_release_ts_feed; + dmx->allocate_section_feed = dvbdmx_allocate_section_feed; + dmx->release_section_feed = dvbdmx_release_section_feed; + + dmx->add_frontend = dvbdmx_add_frontend; + dmx->remove_frontend = dvbdmx_remove_frontend; + dmx->get_frontends = dvbdmx_get_frontends; + dmx->connect_frontend = dvbdmx_connect_frontend; + dmx->disconnect_frontend = dvbdmx_disconnect_frontend; + dmx->get_pes_pids = dvbdmx_get_pes_pids; + + mutex_init(&dvbdemux->mutex); + spin_lock_init(&dvbdemux->lock); + + return 0; +} + +EXPORT_SYMBOL(dvb_dmx_init); + +void dvb_dmx_release(struct dvb_demux *dvbdemux) +{ + vfree(dvbdemux->cnt_storage); + vfree(dvbdemux->filter); + vfree(dvbdemux->feed); +} + +EXPORT_SYMBOL(dvb_dmx_release); diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c new file mode 100644 index 000000000..fce0e2094 --- /dev/null +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -0,0 +1,3087 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * dvb_frontend.c: DVB frontend tuning interface/thread + * + * Copyright (C) 1999-2001 Ralph Metzler + * Marcus Metzler + * Holger Waechtler + * for convergence integrated media GmbH + * + * Copyright (C) 2004 Andrew de Quincey (tuning thread cleanup) + */ + +/* Enables DVBv3 compatibility bits at the headers */ +#define __DVB_CORE__ + +#define pr_fmt(fmt) "dvb_frontend: " fmt + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/semaphore.h> +#include <linux/module.h> +#include <linux/nospec.h> +#include <linux/list.h> +#include <linux/freezer.h> +#include <linux/jiffies.h> +#include <linux/kthread.h> +#include <linux/ktime.h> +#include <linux/compat.h> +#include <asm/processor.h> + +#include <media/dvb_frontend.h> +#include <media/dvbdev.h> +#include <linux/dvb/version.h> + +static int dvb_frontend_debug; +static int dvb_shutdown_timeout; +static int dvb_force_auto_inversion; +static int dvb_override_tune_delay; +static int dvb_powerdown_on_sleep = 1; +static int dvb_mfe_wait_time = 5; + +module_param_named(frontend_debug, dvb_frontend_debug, int, 0644); +MODULE_PARM_DESC(frontend_debug, "Turn on/off frontend core debugging (default:off)."); +module_param(dvb_shutdown_timeout, int, 0644); +MODULE_PARM_DESC(dvb_shutdown_timeout, "wait <shutdown_timeout> seconds after close() before suspending hardware"); +module_param(dvb_force_auto_inversion, int, 0644); +MODULE_PARM_DESC(dvb_force_auto_inversion, "0: normal (default), 1: INVERSION_AUTO forced always"); +module_param(dvb_override_tune_delay, int, 0644); +MODULE_PARM_DESC(dvb_override_tune_delay, "0: normal (default), >0 => delay in milliseconds to wait for lock after a tune attempt"); +module_param(dvb_powerdown_on_sleep, int, 0644); +MODULE_PARM_DESC(dvb_powerdown_on_sleep, "0: do not power down, 1: turn LNB voltage off on sleep (default)"); +module_param(dvb_mfe_wait_time, int, 0644); +MODULE_PARM_DESC(dvb_mfe_wait_time, "Wait up to <mfe_wait_time> seconds on open() for multi-frontend to become available (default:5 seconds)"); + +#define dprintk(fmt, arg...) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), __func__, ##arg) + +#define FESTATE_IDLE 1 +#define FESTATE_RETUNE 2 +#define FESTATE_TUNING_FAST 4 +#define FESTATE_TUNING_SLOW 8 +#define FESTATE_TUNED 16 +#define FESTATE_ZIGZAG_FAST 32 +#define FESTATE_ZIGZAG_SLOW 64 +#define FESTATE_DISEQC 128 +#define FESTATE_ERROR 256 +#define FESTATE_WAITFORLOCK (FESTATE_TUNING_FAST | FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW | FESTATE_DISEQC) +#define FESTATE_SEARCHING_FAST (FESTATE_TUNING_FAST | FESTATE_ZIGZAG_FAST) +#define FESTATE_SEARCHING_SLOW (FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_SLOW) +#define FESTATE_LOSTLOCK (FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW) + +/* + * FESTATE_IDLE. No tuning parameters have been supplied and the loop is idling. + * FESTATE_RETUNE. Parameters have been supplied, but we have not yet performed the first tune. + * FESTATE_TUNING_FAST. Tuning parameters have been supplied and fast zigzag scan is in progress. + * FESTATE_TUNING_SLOW. Tuning parameters have been supplied. Fast zigzag failed, so we're trying again, but slower. + * FESTATE_TUNED. The frontend has successfully locked on. + * FESTATE_ZIGZAG_FAST. The lock has been lost, and a fast zigzag has been initiated to try and regain it. + * FESTATE_ZIGZAG_SLOW. The lock has been lost. Fast zigzag has been failed, so we're trying again, but slower. + * FESTATE_DISEQC. A DISEQC command has just been issued. + * FESTATE_WAITFORLOCK. When we're waiting for a lock. + * FESTATE_SEARCHING_FAST. When we're searching for a signal using a fast zigzag scan. + * FESTATE_SEARCHING_SLOW. When we're searching for a signal using a slow zigzag scan. + * FESTATE_LOSTLOCK. When the lock has been lost, and we're searching it again. + */ + +static DEFINE_MUTEX(frontend_mutex); + +struct dvb_frontend_private { + /* thread/frontend values */ + struct dvb_device *dvbdev; + struct dvb_frontend_parameters parameters_out; + struct dvb_fe_events events; + struct semaphore sem; + struct list_head list_head; + wait_queue_head_t wait_queue; + struct task_struct *thread; + unsigned long release_jiffies; + unsigned int wakeup; + enum fe_status status; + unsigned long tune_mode_flags; + unsigned int delay; + unsigned int reinitialise; + int tone; + int voltage; + + /* swzigzag values */ + unsigned int state; + unsigned int bending; + int lnb_drift; + unsigned int inversion; + unsigned int auto_step; + unsigned int auto_sub_step; + unsigned int started_auto_step; + unsigned int min_delay; + unsigned int max_drift; + unsigned int step_size; + int quality; + unsigned int check_wrapped; + enum dvbfe_search algo_status; + +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + struct media_pipeline pipe; +#endif +}; + +static void dvb_frontend_invoke_release(struct dvb_frontend *fe, + void (*release)(struct dvb_frontend *fe)); + +static void __dvb_frontend_free(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + if (fepriv) + dvb_device_put(fepriv->dvbdev); + + dvb_frontend_invoke_release(fe, fe->ops.release); + + kfree(fepriv); +} + +static void dvb_frontend_free(struct kref *ref) +{ + struct dvb_frontend *fe = + container_of(ref, struct dvb_frontend, refcount); + + __dvb_frontend_free(fe); +} + +static void dvb_frontend_put(struct dvb_frontend *fe) +{ + /* call detach before dropping the reference count */ + if (fe->ops.detach) + fe->ops.detach(fe); + /* + * Check if the frontend was registered, as otherwise + * kref was not initialized yet. + */ + if (fe->frontend_priv) + kref_put(&fe->refcount, dvb_frontend_free); + else + __dvb_frontend_free(fe); +} + +static void dvb_frontend_get(struct dvb_frontend *fe) +{ + kref_get(&fe->refcount); +} + +static void dvb_frontend_wakeup(struct dvb_frontend *fe); +static int dtv_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *c, + struct dvb_frontend_parameters *p_out); +static int +dtv_property_legacy_params_sync(struct dvb_frontend *fe, + const struct dtv_frontend_properties *c, + struct dvb_frontend_parameters *p); + +static bool has_get_frontend(struct dvb_frontend *fe) +{ + return fe->ops.get_frontend; +} + +/* + * Due to DVBv3 API calls, a delivery system should be mapped into one of + * the 4 DVBv3 delivery systems (FE_QPSK, FE_QAM, FE_OFDM or FE_ATSC), + * otherwise, a DVBv3 call will fail. + */ +enum dvbv3_emulation_type { + DVBV3_UNKNOWN, + DVBV3_QPSK, + DVBV3_QAM, + DVBV3_OFDM, + DVBV3_ATSC, +}; + +static enum dvbv3_emulation_type dvbv3_type(u32 delivery_system) +{ + switch (delivery_system) { + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + return DVBV3_QAM; + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + case SYS_ISDBS: + case SYS_DSS: + return DVBV3_QPSK; + case SYS_DVBT: + case SYS_DVBT2: + case SYS_ISDBT: + case SYS_DTMB: + return DVBV3_OFDM; + case SYS_ATSC: + case SYS_ATSCMH: + case SYS_DVBC_ANNEX_B: + return DVBV3_ATSC; + case SYS_UNDEFINED: + case SYS_ISDBC: + case SYS_DVBH: + case SYS_DAB: + default: + /* + * Doesn't know how to emulate those types and/or + * there's no frontend driver from this type yet + * with some emulation code, so, we're not sure yet how + * to handle them, or they're not compatible with a DVBv3 call. + */ + return DVBV3_UNKNOWN; + } +} + +static void dvb_frontend_add_event(struct dvb_frontend *fe, + enum fe_status status) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_fe_events *events = &fepriv->events; + struct dvb_frontend_event *e; + int wp; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if ((status & FE_HAS_LOCK) && has_get_frontend(fe)) + dtv_get_frontend(fe, c, &fepriv->parameters_out); + + mutex_lock(&events->mtx); + + wp = (events->eventw + 1) % MAX_EVENT; + if (wp == events->eventr) { + events->overflow = 1; + events->eventr = (events->eventr + 1) % MAX_EVENT; + } + + e = &events->events[events->eventw]; + e->status = status; + e->parameters = fepriv->parameters_out; + + events->eventw = wp; + + mutex_unlock(&events->mtx); + + wake_up_interruptible(&events->wait_queue); +} + +static int dvb_frontend_test_event(struct dvb_frontend_private *fepriv, + struct dvb_fe_events *events) +{ + int ret; + + up(&fepriv->sem); + ret = events->eventw != events->eventr; + down(&fepriv->sem); + + return ret; +} + +static int dvb_frontend_get_event(struct dvb_frontend *fe, + struct dvb_frontend_event *event, int flags) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if (events->overflow) { + events->overflow = 0; + return -EOVERFLOW; + } + + if (events->eventw == events->eventr) { + struct wait_queue_entry wait; + int ret = 0; + + if (flags & O_NONBLOCK) + return -EWOULDBLOCK; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&events->wait_queue, &wait); + while (!dvb_frontend_test_event(fepriv, events)) { + wait_woken(&wait, TASK_INTERRUPTIBLE, 0); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&events->wait_queue, &wait); + if (ret < 0) + return ret; + } + + mutex_lock(&events->mtx); + *event = events->events[events->eventr]; + events->eventr = (events->eventr + 1) % MAX_EVENT; + mutex_unlock(&events->mtx); + + return 0; +} + +static void dvb_frontend_clear_events(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + + mutex_lock(&events->mtx); + events->eventr = events->eventw; + mutex_unlock(&events->mtx); +} + +static void dvb_frontend_init(struct dvb_frontend *fe) +{ + dev_dbg(fe->dvb->device, + "%s: initialising adapter %i frontend %i (%s)...\n", + __func__, fe->dvb->num, fe->id, fe->ops.info.name); + + if (fe->ops.init) + fe->ops.init(fe); + if (fe->ops.tuner_ops.init) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + fe->ops.tuner_ops.init(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } +} + +void dvb_frontend_reinitialise(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + fepriv->reinitialise = 1; + dvb_frontend_wakeup(fe); +} +EXPORT_SYMBOL(dvb_frontend_reinitialise); + +static void dvb_frontend_swzigzag_update_delay(struct dvb_frontend_private *fepriv, int locked) +{ + int q2; + struct dvb_frontend *fe = fepriv->dvbdev->priv; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if (locked) + (fepriv->quality) = (fepriv->quality * 220 + 36 * 256) / 256; + else + (fepriv->quality) = (fepriv->quality * 220 + 0) / 256; + + q2 = fepriv->quality - 128; + q2 *= q2; + + fepriv->delay = fepriv->min_delay + q2 * HZ / (128 * 128); +} + +/** + * dvb_frontend_swzigzag_autotune - Performs automatic twiddling of frontend + * parameters. + * + * @fe: The frontend concerned. + * @check_wrapped: Checks if an iteration has completed. + * DO NOT SET ON THE FIRST ATTEMPT. + * + * return: Number of complete iterations that have been performed. + */ +static int dvb_frontend_swzigzag_autotune(struct dvb_frontend *fe, int check_wrapped) +{ + int autoinversion; + int ready = 0; + int fe_set_err = 0; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache, tmp; + int original_inversion = c->inversion; + u32 original_frequency = c->frequency; + + /* are we using autoinversion? */ + autoinversion = ((!(fe->ops.info.caps & FE_CAN_INVERSION_AUTO)) && + (c->inversion == INVERSION_AUTO)); + + /* setup parameters correctly */ + while (!ready) { + /* calculate the lnb_drift */ + fepriv->lnb_drift = fepriv->auto_step * fepriv->step_size; + + /* wrap the auto_step if we've exceeded the maximum drift */ + if (fepriv->lnb_drift > fepriv->max_drift) { + fepriv->auto_step = 0; + fepriv->auto_sub_step = 0; + fepriv->lnb_drift = 0; + } + + /* perform inversion and +/- zigzag */ + switch (fepriv->auto_sub_step) { + case 0: + /* try with the current inversion and current drift setting */ + ready = 1; + break; + + case 1: + if (!autoinversion) break; + + fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; + ready = 1; + break; + + case 2: + if (fepriv->lnb_drift == 0) break; + + fepriv->lnb_drift = -fepriv->lnb_drift; + ready = 1; + break; + + case 3: + if (fepriv->lnb_drift == 0) break; + if (!autoinversion) break; + + fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; + fepriv->lnb_drift = -fepriv->lnb_drift; + ready = 1; + break; + + default: + fepriv->auto_step++; + fepriv->auto_sub_step = -1; /* it'll be incremented to 0 in a moment */ + break; + } + + if (!ready) fepriv->auto_sub_step++; + } + + /* if this attempt would hit where we started, indicate a complete + * iteration has occurred */ + if ((fepriv->auto_step == fepriv->started_auto_step) && + (fepriv->auto_sub_step == 0) && check_wrapped) { + return 1; + } + + dev_dbg(fe->dvb->device, + "%s: drift:%i inversion:%i auto_step:%i auto_sub_step:%i started_auto_step:%i\n", + __func__, fepriv->lnb_drift, fepriv->inversion, + fepriv->auto_step, fepriv->auto_sub_step, + fepriv->started_auto_step); + + /* set the frontend itself */ + c->frequency += fepriv->lnb_drift; + if (autoinversion) + c->inversion = fepriv->inversion; + tmp = *c; + if (fe->ops.set_frontend) + fe_set_err = fe->ops.set_frontend(fe); + *c = tmp; + if (fe_set_err < 0) { + fepriv->state = FESTATE_ERROR; + return fe_set_err; + } + + c->frequency = original_frequency; + c->inversion = original_inversion; + + fepriv->auto_sub_step++; + return 0; +} + +static void dvb_frontend_swzigzag(struct dvb_frontend *fe) +{ + enum fe_status s = FE_NONE; + int retval = 0; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache, tmp; + + if (fepriv->max_drift) + dev_warn_once(fe->dvb->device, + "Frontend requested software zigzag, but didn't set the frequency step size\n"); + + /* if we've got no parameters, just keep idling */ + if (fepriv->state & FESTATE_IDLE) { + fepriv->delay = 3 * HZ; + fepriv->quality = 0; + return; + } + + /* in SCAN mode, we just set the frontend when asked and leave it alone */ + if (fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT) { + if (fepriv->state & FESTATE_RETUNE) { + tmp = *c; + if (fe->ops.set_frontend) + retval = fe->ops.set_frontend(fe); + *c = tmp; + if (retval < 0) + fepriv->state = FESTATE_ERROR; + else + fepriv->state = FESTATE_TUNED; + } + fepriv->delay = 3 * HZ; + fepriv->quality = 0; + return; + } + + /* get the frontend status */ + if (fepriv->state & FESTATE_RETUNE) { + s = 0; + } else { + if (fe->ops.read_status) + fe->ops.read_status(fe, &s); + if (s != fepriv->status) { + dvb_frontend_add_event(fe, s); + fepriv->status = s; + } + } + + /* if we're not tuned, and we have a lock, move to the TUNED state */ + if ((fepriv->state & FESTATE_WAITFORLOCK) && (s & FE_HAS_LOCK)) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + fepriv->state = FESTATE_TUNED; + + /* if we're tuned, then we have determined the correct inversion */ + if ((!(fe->ops.info.caps & FE_CAN_INVERSION_AUTO)) && + (c->inversion == INVERSION_AUTO)) { + c->inversion = fepriv->inversion; + } + return; + } + + /* if we are tuned already, check we're still locked */ + if (fepriv->state & FESTATE_TUNED) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + + /* we're tuned, and the lock is still good... */ + if (s & FE_HAS_LOCK) { + return; + } else { /* if we _WERE_ tuned, but now don't have a lock */ + fepriv->state = FESTATE_ZIGZAG_FAST; + fepriv->started_auto_step = fepriv->auto_step; + fepriv->check_wrapped = 0; + } + } + + /* don't actually do anything if we're in the LOSTLOCK state, + * the frontend is set to FE_CAN_RECOVER, and the max_drift is 0 */ + if ((fepriv->state & FESTATE_LOSTLOCK) && + (fe->ops.info.caps & FE_CAN_RECOVER) && (fepriv->max_drift == 0)) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + return; + } + + /* don't do anything if we're in the DISEQC state, since this + * might be someone with a motorized dish controlled by DISEQC. + * If its actually a re-tune, there will be a SET_FRONTEND soon enough. */ + if (fepriv->state & FESTATE_DISEQC) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + return; + } + + /* if we're in the RETUNE state, set everything up for a brand + * new scan, keeping the current inversion setting, as the next + * tune is _very_ likely to require the same */ + if (fepriv->state & FESTATE_RETUNE) { + fepriv->lnb_drift = 0; + fepriv->auto_step = 0; + fepriv->auto_sub_step = 0; + fepriv->started_auto_step = 0; + fepriv->check_wrapped = 0; + } + + /* fast zigzag. */ + if ((fepriv->state & FESTATE_SEARCHING_FAST) || (fepriv->state & FESTATE_RETUNE)) { + fepriv->delay = fepriv->min_delay; + + /* perform a tune */ + retval = dvb_frontend_swzigzag_autotune(fe, + fepriv->check_wrapped); + if (retval < 0) { + return; + } else if (retval) { + /* OK, if we've run out of trials at the fast speed. + * Drop back to slow for the _next_ attempt */ + fepriv->state = FESTATE_SEARCHING_SLOW; + fepriv->started_auto_step = fepriv->auto_step; + return; + } + fepriv->check_wrapped = 1; + + /* if we've just re-tuned, enter the ZIGZAG_FAST state. + * This ensures we cannot return from an + * FE_SET_FRONTEND ioctl before the first frontend tune + * occurs */ + if (fepriv->state & FESTATE_RETUNE) { + fepriv->state = FESTATE_TUNING_FAST; + } + } + + /* slow zigzag */ + if (fepriv->state & FESTATE_SEARCHING_SLOW) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + + /* Note: don't bother checking for wrapping; we stay in this + * state until we get a lock */ + dvb_frontend_swzigzag_autotune(fe, 0); + } +} + +static int dvb_frontend_is_exiting(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + if (fe->exit != DVB_FE_NO_EXIT) + return 1; + + if (fepriv->dvbdev->writers == 1) + if (time_after_eq(jiffies, fepriv->release_jiffies + + dvb_shutdown_timeout * HZ)) + return 1; + + return 0; +} + +static int dvb_frontend_should_wakeup(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + if (fepriv->wakeup) { + fepriv->wakeup = 0; + return 1; + } + return dvb_frontend_is_exiting(fe); +} + +static void dvb_frontend_wakeup(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + fepriv->wakeup = 1; + wake_up_interruptible(&fepriv->wait_queue); +} + +static int dvb_frontend_thread(void *data) +{ + struct dvb_frontend *fe = data; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + enum fe_status s = FE_NONE; + enum dvbfe_algo algo; + bool re_tune = false; + bool semheld = false; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + fepriv->check_wrapped = 0; + fepriv->quality = 0; + fepriv->delay = 3 * HZ; + fepriv->status = 0; + fepriv->wakeup = 0; + fepriv->reinitialise = 0; + + dvb_frontend_init(fe); + + set_freezable(); + while (1) { + up(&fepriv->sem); /* is locked when we enter the thread... */ +restart: + wait_event_interruptible_timeout(fepriv->wait_queue, + dvb_frontend_should_wakeup(fe) || + kthread_should_stop() || + freezing(current), + fepriv->delay); + + if (kthread_should_stop() || dvb_frontend_is_exiting(fe)) { + /* got signal or quitting */ + if (!down_interruptible(&fepriv->sem)) + semheld = true; + fe->exit = DVB_FE_NORMAL_EXIT; + break; + } + + if (try_to_freeze()) + goto restart; + + if (down_interruptible(&fepriv->sem)) + break; + + if (fepriv->reinitialise) { + dvb_frontend_init(fe); + if (fe->ops.set_tone && fepriv->tone != -1) + fe->ops.set_tone(fe, fepriv->tone); + if (fe->ops.set_voltage && fepriv->voltage != -1) + fe->ops.set_voltage(fe, fepriv->voltage); + fepriv->reinitialise = 0; + } + + /* do an iteration of the tuning loop */ + if (fe->ops.get_frontend_algo) { + algo = fe->ops.get_frontend_algo(fe); + switch (algo) { + case DVBFE_ALGO_HW: + dev_dbg(fe->dvb->device, "%s: Frontend ALGO = DVBFE_ALGO_HW\n", __func__); + + if (fepriv->state & FESTATE_RETUNE) { + dev_dbg(fe->dvb->device, "%s: Retune requested, FESTATE_RETUNE\n", __func__); + re_tune = true; + fepriv->state = FESTATE_TUNED; + } else { + re_tune = false; + } + + if (fe->ops.tune) + fe->ops.tune(fe, re_tune, fepriv->tune_mode_flags, &fepriv->delay, &s); + + if (s != fepriv->status && !(fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT)) { + dev_dbg(fe->dvb->device, "%s: state changed, adding current state\n", __func__); + dvb_frontend_add_event(fe, s); + fepriv->status = s; + } + break; + case DVBFE_ALGO_SW: + dev_dbg(fe->dvb->device, "%s: Frontend ALGO = DVBFE_ALGO_SW\n", __func__); + dvb_frontend_swzigzag(fe); + break; + case DVBFE_ALGO_CUSTOM: + dev_dbg(fe->dvb->device, "%s: Frontend ALGO = DVBFE_ALGO_CUSTOM, state=%d\n", __func__, fepriv->state); + if (fepriv->state & FESTATE_RETUNE) { + dev_dbg(fe->dvb->device, "%s: Retune requested, FESTAT_RETUNE\n", __func__); + fepriv->state = FESTATE_TUNED; + } + /* Case where we are going to search for a carrier + * User asked us to retune again for some reason, possibly + * requesting a search with a new set of parameters + */ + if (fepriv->algo_status & DVBFE_ALGO_SEARCH_AGAIN) { + if (fe->ops.search) { + fepriv->algo_status = fe->ops.search(fe); + /* We did do a search as was requested, the flags are + * now unset as well and has the flags wrt to search. + */ + } else { + fepriv->algo_status &= ~DVBFE_ALGO_SEARCH_AGAIN; + } + } + /* Track the carrier if the search was successful */ + if (fepriv->algo_status != DVBFE_ALGO_SEARCH_SUCCESS) { + fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; + fepriv->delay = HZ / 2; + } + dtv_property_legacy_params_sync(fe, c, &fepriv->parameters_out); + fe->ops.read_status(fe, &s); + if (s != fepriv->status) { + dvb_frontend_add_event(fe, s); /* update event list */ + fepriv->status = s; + if (!(s & FE_HAS_LOCK)) { + fepriv->delay = HZ / 10; + fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; + } else { + fepriv->delay = 60 * HZ; + } + } + break; + default: + dev_dbg(fe->dvb->device, "%s: UNDEFINED ALGO !\n", __func__); + break; + } + } else { + dvb_frontend_swzigzag(fe); + } + } + + if (dvb_powerdown_on_sleep) { + if (fe->ops.set_voltage) + fe->ops.set_voltage(fe, SEC_VOLTAGE_OFF); + if (fe->ops.tuner_ops.sleep) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + fe->ops.tuner_ops.sleep(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + if (fe->ops.sleep) + fe->ops.sleep(fe); + } + + fepriv->thread = NULL; + if (kthread_should_stop()) + fe->exit = DVB_FE_DEVICE_REMOVED; + else + fe->exit = DVB_FE_NO_EXIT; + mb(); + + if (semheld) + up(&fepriv->sem); + dvb_frontend_wakeup(fe); + return 0; +} + +static void dvb_frontend_stop(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if (fe->exit != DVB_FE_DEVICE_REMOVED) + fe->exit = DVB_FE_NORMAL_EXIT; + mb(); + + if (!fepriv->thread) + return; + + kthread_stop(fepriv->thread); + + sema_init(&fepriv->sem, 1); + fepriv->state = FESTATE_IDLE; + + /* paranoia check in case a signal arrived */ + if (fepriv->thread) + dev_warn(fe->dvb->device, + "dvb_frontend_stop: warning: thread %p won't exit\n", + fepriv->thread); +} + +/* + * Sleep for the amount of time given by add_usec parameter + * + * This needs to be as precise as possible, as it affects the detection of + * the dish tone command at the satellite subsystem. The precision is improved + * by using a scheduled msleep followed by udelay for the remainder. + */ +void dvb_frontend_sleep_until(ktime_t *waketime, u32 add_usec) +{ + s32 delta; + + *waketime = ktime_add_us(*waketime, add_usec); + delta = ktime_us_delta(ktime_get_boottime(), *waketime); + if (delta > 2500) { + msleep((delta - 1500) / 1000); + delta = ktime_us_delta(ktime_get_boottime(), *waketime); + } + if (delta > 0) + udelay(delta); +} +EXPORT_SYMBOL(dvb_frontend_sleep_until); + +static int dvb_frontend_start(struct dvb_frontend *fe) +{ + int ret; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct task_struct *fe_thread; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if (fepriv->thread) { + if (fe->exit == DVB_FE_NO_EXIT) + return 0; + else + dvb_frontend_stop(fe); + } + + if (signal_pending(current)) + return -EINTR; + if (down_interruptible(&fepriv->sem)) + return -EINTR; + + fepriv->state = FESTATE_IDLE; + fe->exit = DVB_FE_NO_EXIT; + fepriv->thread = NULL; + mb(); + + fe_thread = kthread_run(dvb_frontend_thread, fe, + "kdvb-ad-%i-fe-%i", fe->dvb->num, fe->id); + if (IS_ERR(fe_thread)) { + ret = PTR_ERR(fe_thread); + dev_warn(fe->dvb->device, + "dvb_frontend_start: failed to start kthread (%d)\n", + ret); + up(&fepriv->sem); + return ret; + } + fepriv->thread = fe_thread; + return 0; +} + +static void dvb_frontend_get_frequency_limits(struct dvb_frontend *fe, + u32 *freq_min, u32 *freq_max, + u32 *tolerance) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 tuner_min = fe->ops.tuner_ops.info.frequency_min_hz; + u32 tuner_max = fe->ops.tuner_ops.info.frequency_max_hz; + u32 frontend_min = fe->ops.info.frequency_min_hz; + u32 frontend_max = fe->ops.info.frequency_max_hz; + + *freq_min = max(frontend_min, tuner_min); + + if (frontend_max == 0) + *freq_max = tuner_max; + else if (tuner_max == 0) + *freq_max = frontend_max; + else + *freq_max = min(frontend_max, tuner_max); + + if (*freq_min == 0 || *freq_max == 0) + dev_warn(fe->dvb->device, + "DVB: adapter %i frontend %u frequency limits undefined - fix the driver\n", + fe->dvb->num, fe->id); + + dev_dbg(fe->dvb->device, "frequency interval: tuner: %u...%u, frontend: %u...%u", + tuner_min, tuner_max, frontend_min, frontend_max); + + /* If the standard is for satellite, convert frequencies to kHz */ + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + case SYS_ISDBS: + *freq_min /= kHz; + *freq_max /= kHz; + if (tolerance) + *tolerance = fe->ops.info.frequency_tolerance_hz / kHz; + + break; + default: + if (tolerance) + *tolerance = fe->ops.info.frequency_tolerance_hz; + break; + } +} + +static u32 dvb_frontend_get_stepsize(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 fe_step = fe->ops.info.frequency_stepsize_hz; + u32 tuner_step = fe->ops.tuner_ops.info.frequency_step_hz; + u32 step = max(fe_step, tuner_step); + + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + case SYS_ISDBS: + step /= kHz; + break; + default: + break; + } + + return step; +} + +static int dvb_frontend_check_parameters(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 freq_min; + u32 freq_max; + + /* range check: frequency */ + dvb_frontend_get_frequency_limits(fe, &freq_min, &freq_max, NULL); + if ((freq_min && c->frequency < freq_min) || + (freq_max && c->frequency > freq_max)) { + dev_warn(fe->dvb->device, "DVB: adapter %i frontend %i frequency %u out of range (%u..%u)\n", + fe->dvb->num, fe->id, c->frequency, + freq_min, freq_max); + return -EINVAL; + } + + /* range check: symbol rate */ + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + if ((fe->ops.info.symbol_rate_min && + c->symbol_rate < fe->ops.info.symbol_rate_min) || + (fe->ops.info.symbol_rate_max && + c->symbol_rate > fe->ops.info.symbol_rate_max)) { + dev_warn(fe->dvb->device, "DVB: adapter %i frontend %i symbol rate %u out of range (%u..%u)\n", + fe->dvb->num, fe->id, c->symbol_rate, + fe->ops.info.symbol_rate_min, + fe->ops.info.symbol_rate_max); + return -EINVAL; + } + break; + default: + break; + } + + return 0; +} + +static int dvb_frontend_clear_cache(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int i; + u32 delsys; + + delsys = c->delivery_system; + memset(c, 0, offsetof(struct dtv_frontend_properties, strength)); + c->delivery_system = delsys; + + dev_dbg(fe->dvb->device, "%s: Clearing cache for delivery system %d\n", + __func__, c->delivery_system); + + c->transmission_mode = TRANSMISSION_MODE_AUTO; + c->bandwidth_hz = 0; /* AUTO */ + c->guard_interval = GUARD_INTERVAL_AUTO; + c->hierarchy = HIERARCHY_AUTO; + c->symbol_rate = 0; + c->code_rate_HP = FEC_AUTO; + c->code_rate_LP = FEC_AUTO; + c->fec_inner = FEC_AUTO; + c->rolloff = ROLLOFF_AUTO; + c->voltage = SEC_VOLTAGE_OFF; + c->sectone = SEC_TONE_OFF; + c->pilot = PILOT_AUTO; + + c->isdbt_partial_reception = 0; + c->isdbt_sb_mode = 0; + c->isdbt_sb_subchannel = 0; + c->isdbt_sb_segment_idx = 0; + c->isdbt_sb_segment_count = 0; + c->isdbt_layer_enabled = 7; /* All layers (A,B,C) */ + for (i = 0; i < 3; i++) { + c->layer[i].fec = FEC_AUTO; + c->layer[i].modulation = QAM_AUTO; + c->layer[i].interleaving = 0; + c->layer[i].segment_count = 0; + } + + c->stream_id = NO_STREAM_ID_FILTER; + c->scrambling_sequence_index = 0;/* default sequence */ + + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + c->modulation = QPSK; /* implied for DVB-S in legacy API */ + c->rolloff = ROLLOFF_35;/* implied for DVB-S */ + break; + case SYS_ATSC: + c->modulation = VSB_8; + break; + case SYS_ISDBS: + c->symbol_rate = 28860000; + c->rolloff = ROLLOFF_35; + c->bandwidth_hz = c->symbol_rate / 100 * 135; + break; + default: + c->modulation = QAM_AUTO; + break; + } + + c->lna = LNA_AUTO; + + return 0; +} + +#define _DTV_CMD(n) \ + [n] = #n + +static char *dtv_cmds[DTV_MAX_COMMAND + 1] = { + _DTV_CMD(DTV_TUNE), + _DTV_CMD(DTV_CLEAR), + + /* Set */ + _DTV_CMD(DTV_FREQUENCY), + _DTV_CMD(DTV_BANDWIDTH_HZ), + _DTV_CMD(DTV_MODULATION), + _DTV_CMD(DTV_INVERSION), + _DTV_CMD(DTV_DISEQC_MASTER), + _DTV_CMD(DTV_SYMBOL_RATE), + _DTV_CMD(DTV_INNER_FEC), + _DTV_CMD(DTV_VOLTAGE), + _DTV_CMD(DTV_TONE), + _DTV_CMD(DTV_PILOT), + _DTV_CMD(DTV_ROLLOFF), + _DTV_CMD(DTV_DELIVERY_SYSTEM), + _DTV_CMD(DTV_HIERARCHY), + _DTV_CMD(DTV_CODE_RATE_HP), + _DTV_CMD(DTV_CODE_RATE_LP), + _DTV_CMD(DTV_GUARD_INTERVAL), + _DTV_CMD(DTV_TRANSMISSION_MODE), + _DTV_CMD(DTV_INTERLEAVING), + + _DTV_CMD(DTV_ISDBT_PARTIAL_RECEPTION), + _DTV_CMD(DTV_ISDBT_SOUND_BROADCASTING), + _DTV_CMD(DTV_ISDBT_SB_SUBCHANNEL_ID), + _DTV_CMD(DTV_ISDBT_SB_SEGMENT_IDX), + _DTV_CMD(DTV_ISDBT_SB_SEGMENT_COUNT), + _DTV_CMD(DTV_ISDBT_LAYER_ENABLED), + _DTV_CMD(DTV_ISDBT_LAYERA_FEC), + _DTV_CMD(DTV_ISDBT_LAYERA_MODULATION), + _DTV_CMD(DTV_ISDBT_LAYERA_SEGMENT_COUNT), + _DTV_CMD(DTV_ISDBT_LAYERA_TIME_INTERLEAVING), + _DTV_CMD(DTV_ISDBT_LAYERB_FEC), + _DTV_CMD(DTV_ISDBT_LAYERB_MODULATION), + _DTV_CMD(DTV_ISDBT_LAYERB_SEGMENT_COUNT), + _DTV_CMD(DTV_ISDBT_LAYERB_TIME_INTERLEAVING), + _DTV_CMD(DTV_ISDBT_LAYERC_FEC), + _DTV_CMD(DTV_ISDBT_LAYERC_MODULATION), + _DTV_CMD(DTV_ISDBT_LAYERC_SEGMENT_COUNT), + _DTV_CMD(DTV_ISDBT_LAYERC_TIME_INTERLEAVING), + + _DTV_CMD(DTV_STREAM_ID), + _DTV_CMD(DTV_DVBT2_PLP_ID_LEGACY), + _DTV_CMD(DTV_SCRAMBLING_SEQUENCE_INDEX), + _DTV_CMD(DTV_LNA), + + /* Get */ + _DTV_CMD(DTV_DISEQC_SLAVE_REPLY), + _DTV_CMD(DTV_API_VERSION), + + _DTV_CMD(DTV_ENUM_DELSYS), + + _DTV_CMD(DTV_ATSCMH_PARADE_ID), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_ENSEMBLE), + + _DTV_CMD(DTV_ATSCMH_FIC_VER), + _DTV_CMD(DTV_ATSCMH_NOG), + _DTV_CMD(DTV_ATSCMH_TNOG), + _DTV_CMD(DTV_ATSCMH_SGN), + _DTV_CMD(DTV_ATSCMH_PRC), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_MODE), + _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_PRI), + _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_SEC), + _DTV_CMD(DTV_ATSCMH_SCCC_BLOCK_MODE), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_A), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_B), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_C), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_D), + + /* Statistics API */ + _DTV_CMD(DTV_STAT_SIGNAL_STRENGTH), + _DTV_CMD(DTV_STAT_CNR), + _DTV_CMD(DTV_STAT_PRE_ERROR_BIT_COUNT), + _DTV_CMD(DTV_STAT_PRE_TOTAL_BIT_COUNT), + _DTV_CMD(DTV_STAT_POST_ERROR_BIT_COUNT), + _DTV_CMD(DTV_STAT_POST_TOTAL_BIT_COUNT), + _DTV_CMD(DTV_STAT_ERROR_BLOCK_COUNT), + _DTV_CMD(DTV_STAT_TOTAL_BLOCK_COUNT), +}; + +static char *dtv_cmd_name(u32 cmd) +{ + cmd = array_index_nospec(cmd, DTV_MAX_COMMAND); + return dtv_cmds[cmd]; +} + +/* Synchronise the legacy tuning parameters into the cache, so that demodulator + * drivers can use a single set_frontend tuning function, regardless of whether + * it's being used for the legacy or new API, reducing code and complexity. + */ +static int dtv_property_cache_sync(struct dvb_frontend *fe, + struct dtv_frontend_properties *c, + const struct dvb_frontend_parameters *p) +{ + c->frequency = p->frequency; + c->inversion = p->inversion; + + switch (dvbv3_type(c->delivery_system)) { + case DVBV3_QPSK: + dev_dbg(fe->dvb->device, "%s: Preparing QPSK req\n", __func__); + c->symbol_rate = p->u.qpsk.symbol_rate; + c->fec_inner = p->u.qpsk.fec_inner; + break; + case DVBV3_QAM: + dev_dbg(fe->dvb->device, "%s: Preparing QAM req\n", __func__); + c->symbol_rate = p->u.qam.symbol_rate; + c->fec_inner = p->u.qam.fec_inner; + c->modulation = p->u.qam.modulation; + break; + case DVBV3_OFDM: + dev_dbg(fe->dvb->device, "%s: Preparing OFDM req\n", __func__); + + switch (p->u.ofdm.bandwidth) { + case BANDWIDTH_10_MHZ: + c->bandwidth_hz = 10000000; + break; + case BANDWIDTH_8_MHZ: + c->bandwidth_hz = 8000000; + break; + case BANDWIDTH_7_MHZ: + c->bandwidth_hz = 7000000; + break; + case BANDWIDTH_6_MHZ: + c->bandwidth_hz = 6000000; + break; + case BANDWIDTH_5_MHZ: + c->bandwidth_hz = 5000000; + break; + case BANDWIDTH_1_712_MHZ: + c->bandwidth_hz = 1712000; + break; + case BANDWIDTH_AUTO: + c->bandwidth_hz = 0; + } + + c->code_rate_HP = p->u.ofdm.code_rate_HP; + c->code_rate_LP = p->u.ofdm.code_rate_LP; + c->modulation = p->u.ofdm.constellation; + c->transmission_mode = p->u.ofdm.transmission_mode; + c->guard_interval = p->u.ofdm.guard_interval; + c->hierarchy = p->u.ofdm.hierarchy_information; + break; + case DVBV3_ATSC: + dev_dbg(fe->dvb->device, "%s: Preparing ATSC req\n", __func__); + c->modulation = p->u.vsb.modulation; + if (c->delivery_system == SYS_ATSCMH) + break; + if ((c->modulation == VSB_8) || (c->modulation == VSB_16)) + c->delivery_system = SYS_ATSC; + else + c->delivery_system = SYS_DVBC_ANNEX_B; + break; + case DVBV3_UNKNOWN: + dev_err(fe->dvb->device, + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + return -EINVAL; + } + + return 0; +} + +/* Ensure the cached values are set correctly in the frontend + * legacy tuning structures, for the advanced tuning API. + */ +static int +dtv_property_legacy_params_sync(struct dvb_frontend *fe, + const struct dtv_frontend_properties *c, + struct dvb_frontend_parameters *p) +{ + p->frequency = c->frequency; + p->inversion = c->inversion; + + switch (dvbv3_type(c->delivery_system)) { + case DVBV3_UNKNOWN: + dev_err(fe->dvb->device, + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + return -EINVAL; + case DVBV3_QPSK: + dev_dbg(fe->dvb->device, "%s: Preparing QPSK req\n", __func__); + p->u.qpsk.symbol_rate = c->symbol_rate; + p->u.qpsk.fec_inner = c->fec_inner; + break; + case DVBV3_QAM: + dev_dbg(fe->dvb->device, "%s: Preparing QAM req\n", __func__); + p->u.qam.symbol_rate = c->symbol_rate; + p->u.qam.fec_inner = c->fec_inner; + p->u.qam.modulation = c->modulation; + break; + case DVBV3_OFDM: + dev_dbg(fe->dvb->device, "%s: Preparing OFDM req\n", __func__); + switch (c->bandwidth_hz) { + case 10000000: + p->u.ofdm.bandwidth = BANDWIDTH_10_MHZ; + break; + case 8000000: + p->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; + break; + case 7000000: + p->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; + break; + case 6000000: + p->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; + break; + case 5000000: + p->u.ofdm.bandwidth = BANDWIDTH_5_MHZ; + break; + case 1712000: + p->u.ofdm.bandwidth = BANDWIDTH_1_712_MHZ; + break; + case 0: + default: + p->u.ofdm.bandwidth = BANDWIDTH_AUTO; + } + p->u.ofdm.code_rate_HP = c->code_rate_HP; + p->u.ofdm.code_rate_LP = c->code_rate_LP; + p->u.ofdm.constellation = c->modulation; + p->u.ofdm.transmission_mode = c->transmission_mode; + p->u.ofdm.guard_interval = c->guard_interval; + p->u.ofdm.hierarchy_information = c->hierarchy; + break; + case DVBV3_ATSC: + dev_dbg(fe->dvb->device, "%s: Preparing VSB req\n", __func__); + p->u.vsb.modulation = c->modulation; + break; + } + return 0; +} + +/** + * dtv_get_frontend - calls a callback for retrieving DTV parameters + * @fe: struct dvb_frontend pointer + * @c: struct dtv_frontend_properties pointer (DVBv5 cache) + * @p_out: struct dvb_frontend_parameters pointer (DVBv3 FE struct) + * + * This routine calls either the DVBv3 or DVBv5 get_frontend call. + * If c is not null, it will update the DVBv5 cache struct pointed by it. + * If p_out is not null, it will update the DVBv3 params pointed by it. + */ +static int dtv_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *c, + struct dvb_frontend_parameters *p_out) +{ + int r; + + if (fe->ops.get_frontend) { + r = fe->ops.get_frontend(fe, c); + if (unlikely(r < 0)) + return r; + if (p_out) + dtv_property_legacy_params_sync(fe, c, p_out); + return 0; + } + + /* As everything is in cache, get_frontend fops are always supported */ + return 0; +} + +static int dvb_frontend_handle_ioctl(struct file *file, + unsigned int cmd, void *parg); + +static int dtv_property_process_get(struct dvb_frontend *fe, + const struct dtv_frontend_properties *c, + struct dtv_property *tvp, + struct file *file) +{ + int ncaps; + unsigned int len = 1; + + switch (tvp->cmd) { + case DTV_ENUM_DELSYS: + ncaps = 0; + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { + tvp->u.buffer.data[ncaps] = fe->ops.delsys[ncaps]; + ncaps++; + } + tvp->u.buffer.len = ncaps; + len = ncaps; + break; + case DTV_FREQUENCY: + tvp->u.data = c->frequency; + break; + case DTV_MODULATION: + tvp->u.data = c->modulation; + break; + case DTV_BANDWIDTH_HZ: + tvp->u.data = c->bandwidth_hz; + break; + case DTV_INVERSION: + tvp->u.data = c->inversion; + break; + case DTV_SYMBOL_RATE: + tvp->u.data = c->symbol_rate; + break; + case DTV_INNER_FEC: + tvp->u.data = c->fec_inner; + break; + case DTV_PILOT: + tvp->u.data = c->pilot; + break; + case DTV_ROLLOFF: + tvp->u.data = c->rolloff; + break; + case DTV_DELIVERY_SYSTEM: + tvp->u.data = c->delivery_system; + break; + case DTV_VOLTAGE: + tvp->u.data = c->voltage; + break; + case DTV_TONE: + tvp->u.data = c->sectone; + break; + case DTV_API_VERSION: + tvp->u.data = (DVB_API_VERSION << 8) | DVB_API_VERSION_MINOR; + break; + case DTV_CODE_RATE_HP: + tvp->u.data = c->code_rate_HP; + break; + case DTV_CODE_RATE_LP: + tvp->u.data = c->code_rate_LP; + break; + case DTV_GUARD_INTERVAL: + tvp->u.data = c->guard_interval; + break; + case DTV_TRANSMISSION_MODE: + tvp->u.data = c->transmission_mode; + break; + case DTV_HIERARCHY: + tvp->u.data = c->hierarchy; + break; + case DTV_INTERLEAVING: + tvp->u.data = c->interleaving; + break; + + /* ISDB-T Support here */ + case DTV_ISDBT_PARTIAL_RECEPTION: + tvp->u.data = c->isdbt_partial_reception; + break; + case DTV_ISDBT_SOUND_BROADCASTING: + tvp->u.data = c->isdbt_sb_mode; + break; + case DTV_ISDBT_SB_SUBCHANNEL_ID: + tvp->u.data = c->isdbt_sb_subchannel; + break; + case DTV_ISDBT_SB_SEGMENT_IDX: + tvp->u.data = c->isdbt_sb_segment_idx; + break; + case DTV_ISDBT_SB_SEGMENT_COUNT: + tvp->u.data = c->isdbt_sb_segment_count; + break; + case DTV_ISDBT_LAYER_ENABLED: + tvp->u.data = c->isdbt_layer_enabled; + break; + case DTV_ISDBT_LAYERA_FEC: + tvp->u.data = c->layer[0].fec; + break; + case DTV_ISDBT_LAYERA_MODULATION: + tvp->u.data = c->layer[0].modulation; + break; + case DTV_ISDBT_LAYERA_SEGMENT_COUNT: + tvp->u.data = c->layer[0].segment_count; + break; + case DTV_ISDBT_LAYERA_TIME_INTERLEAVING: + tvp->u.data = c->layer[0].interleaving; + break; + case DTV_ISDBT_LAYERB_FEC: + tvp->u.data = c->layer[1].fec; + break; + case DTV_ISDBT_LAYERB_MODULATION: + tvp->u.data = c->layer[1].modulation; + break; + case DTV_ISDBT_LAYERB_SEGMENT_COUNT: + tvp->u.data = c->layer[1].segment_count; + break; + case DTV_ISDBT_LAYERB_TIME_INTERLEAVING: + tvp->u.data = c->layer[1].interleaving; + break; + case DTV_ISDBT_LAYERC_FEC: + tvp->u.data = c->layer[2].fec; + break; + case DTV_ISDBT_LAYERC_MODULATION: + tvp->u.data = c->layer[2].modulation; + break; + case DTV_ISDBT_LAYERC_SEGMENT_COUNT: + tvp->u.data = c->layer[2].segment_count; + break; + case DTV_ISDBT_LAYERC_TIME_INTERLEAVING: + tvp->u.data = c->layer[2].interleaving; + break; + + /* Multistream support */ + case DTV_STREAM_ID: + case DTV_DVBT2_PLP_ID_LEGACY: + tvp->u.data = c->stream_id; + break; + + /* Physical layer scrambling support */ + case DTV_SCRAMBLING_SEQUENCE_INDEX: + tvp->u.data = c->scrambling_sequence_index; + break; + + /* ATSC-MH */ + case DTV_ATSCMH_FIC_VER: + tvp->u.data = fe->dtv_property_cache.atscmh_fic_ver; + break; + case DTV_ATSCMH_PARADE_ID: + tvp->u.data = fe->dtv_property_cache.atscmh_parade_id; + break; + case DTV_ATSCMH_NOG: + tvp->u.data = fe->dtv_property_cache.atscmh_nog; + break; + case DTV_ATSCMH_TNOG: + tvp->u.data = fe->dtv_property_cache.atscmh_tnog; + break; + case DTV_ATSCMH_SGN: + tvp->u.data = fe->dtv_property_cache.atscmh_sgn; + break; + case DTV_ATSCMH_PRC: + tvp->u.data = fe->dtv_property_cache.atscmh_prc; + break; + case DTV_ATSCMH_RS_FRAME_MODE: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_frame_mode; + break; + case DTV_ATSCMH_RS_FRAME_ENSEMBLE: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_frame_ensemble; + break; + case DTV_ATSCMH_RS_CODE_MODE_PRI: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_code_mode_pri; + break; + case DTV_ATSCMH_RS_CODE_MODE_SEC: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_code_mode_sec; + break; + case DTV_ATSCMH_SCCC_BLOCK_MODE: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_block_mode; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_A: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_a; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_B: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_b; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_C: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_c; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_D: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_d; + break; + + case DTV_LNA: + tvp->u.data = c->lna; + break; + + /* Fill quality measures */ + case DTV_STAT_SIGNAL_STRENGTH: + tvp->u.st = c->strength; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + case DTV_STAT_CNR: + tvp->u.st = c->cnr; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + case DTV_STAT_PRE_ERROR_BIT_COUNT: + tvp->u.st = c->pre_bit_error; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + case DTV_STAT_PRE_TOTAL_BIT_COUNT: + tvp->u.st = c->pre_bit_count; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + case DTV_STAT_POST_ERROR_BIT_COUNT: + tvp->u.st = c->post_bit_error; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + case DTV_STAT_POST_TOTAL_BIT_COUNT: + tvp->u.st = c->post_bit_count; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + case DTV_STAT_ERROR_BLOCK_COUNT: + tvp->u.st = c->block_error; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + case DTV_STAT_TOTAL_BLOCK_COUNT: + tvp->u.st = c->block_count; + if (tvp->u.buffer.len > MAX_DTV_STATS * sizeof(u32)) + tvp->u.buffer.len = MAX_DTV_STATS * sizeof(u32); + len = tvp->u.buffer.len; + break; + default: + dev_dbg(fe->dvb->device, + "%s: FE property %d doesn't exist\n", + __func__, tvp->cmd); + return -EINVAL; + } + + if (len < 1) + len = 1; + + dev_dbg(fe->dvb->device, + "%s: GET cmd 0x%08x (%s) len %d: %*ph\n", + __func__, tvp->cmd, dtv_cmd_name(tvp->cmd), + tvp->u.buffer.len, tvp->u.buffer.len, tvp->u.buffer.data); + + return 0; +} + +static int dtv_set_frontend(struct dvb_frontend *fe); + +static bool is_dvbv3_delsys(u32 delsys) +{ + return (delsys == SYS_DVBT) || (delsys == SYS_DVBC_ANNEX_A) || + (delsys == SYS_DVBS) || (delsys == SYS_ATSC); +} + +/** + * emulate_delivery_system - emulate a DVBv5 delivery system with a DVBv3 type + * @fe: struct frontend; + * @delsys: DVBv5 type that will be used for emulation + * + * Provides emulation for delivery systems that are compatible with the old + * DVBv3 call. Among its usages, it provices support for ISDB-T, and allows + * using a DVB-S2 only frontend just like it were a DVB-S, if the frontend + * parameters are compatible with DVB-S spec. + */ +static int emulate_delivery_system(struct dvb_frontend *fe, u32 delsys) +{ + int i; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + c->delivery_system = delsys; + + /* + * If the call is for ISDB-T, put it into full-seg, auto mode, TV + */ + if (c->delivery_system == SYS_ISDBT) { + dev_dbg(fe->dvb->device, + "%s: Using defaults for SYS_ISDBT\n", + __func__); + + if (!c->bandwidth_hz) + c->bandwidth_hz = 6000000; + + c->isdbt_partial_reception = 0; + c->isdbt_sb_mode = 0; + c->isdbt_sb_subchannel = 0; + c->isdbt_sb_segment_idx = 0; + c->isdbt_sb_segment_count = 0; + c->isdbt_layer_enabled = 7; + for (i = 0; i < 3; i++) { + c->layer[i].fec = FEC_AUTO; + c->layer[i].modulation = QAM_AUTO; + c->layer[i].interleaving = 0; + c->layer[i].segment_count = 0; + } + } + dev_dbg(fe->dvb->device, "%s: change delivery system on cache to %d\n", + __func__, c->delivery_system); + + return 0; +} + +/** + * dvbv5_set_delivery_system - Sets the delivery system for a DVBv5 API call + * @fe: frontend struct + * @desired_system: delivery system requested by the user + * + * A DVBv5 call know what's the desired system it wants. So, set it. + * + * There are, however, a few known issues with early DVBv5 applications that + * are also handled by this logic: + * + * 1) Some early apps use SYS_UNDEFINED as the desired delivery system. + * This is an API violation, but, as we don't want to break userspace, + * convert it to the first supported delivery system. + * 2) Some apps might be using a DVBv5 call in a wrong way, passing, for + * example, SYS_DVBT instead of SYS_ISDBT. This is because early usage of + * ISDB-T provided backward compat with DVB-T. + */ +static int dvbv5_set_delivery_system(struct dvb_frontend *fe, + u32 desired_system) +{ + int ncaps; + u32 delsys = SYS_UNDEFINED; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + enum dvbv3_emulation_type type; + + /* + * It was reported that some old DVBv5 applications were + * filling delivery_system with SYS_UNDEFINED. If this happens, + * assume that the application wants to use the first supported + * delivery system. + */ + if (desired_system == SYS_UNDEFINED) + desired_system = fe->ops.delsys[0]; + + /* + * This is a DVBv5 call. So, it likely knows the supported + * delivery systems. So, check if the desired delivery system is + * supported + */ + ncaps = 0; + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { + if (fe->ops.delsys[ncaps] == desired_system) { + c->delivery_system = desired_system; + dev_dbg(fe->dvb->device, + "%s: Changing delivery system to %d\n", + __func__, desired_system); + return 0; + } + ncaps++; + } + + /* + * The requested delivery system isn't supported. Maybe userspace + * is requesting a DVBv3 compatible delivery system. + * + * The emulation only works if the desired system is one of the + * delivery systems supported by DVBv3 API + */ + if (!is_dvbv3_delsys(desired_system)) { + dev_dbg(fe->dvb->device, + "%s: Delivery system %d not supported.\n", + __func__, desired_system); + return -EINVAL; + } + + type = dvbv3_type(desired_system); + + /* + * Get the last non-DVBv3 delivery system that has the same type + * of the desired system + */ + ncaps = 0; + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { + if (dvbv3_type(fe->ops.delsys[ncaps]) == type) + delsys = fe->ops.delsys[ncaps]; + ncaps++; + } + + /* There's nothing compatible with the desired delivery system */ + if (delsys == SYS_UNDEFINED) { + dev_dbg(fe->dvb->device, + "%s: Delivery system %d not supported on emulation mode.\n", + __func__, desired_system); + return -EINVAL; + } + + dev_dbg(fe->dvb->device, + "%s: Using delivery system %d emulated as if it were %d\n", + __func__, delsys, desired_system); + + return emulate_delivery_system(fe, desired_system); +} + +/** + * dvbv3_set_delivery_system - Sets the delivery system for a DVBv3 API call + * @fe: frontend struct + * + * A DVBv3 call doesn't know what's the desired system it wants. It also + * doesn't allow to switch between different types. Due to that, userspace + * should use DVBv5 instead. + * However, in order to avoid breaking userspace API, limited backward + * compatibility support is provided. + * + * There are some delivery systems that are incompatible with DVBv3 calls. + * + * This routine should work fine for frontends that support just one delivery + * system. + * + * For frontends that support multiple frontends: + * 1) It defaults to use the first supported delivery system. There's an + * userspace application that allows changing it at runtime; + * + * 2) If the current delivery system is not compatible with DVBv3, it gets + * the first one that it is compatible. + * + * NOTE: in order for this to work with applications like Kaffeine that + * uses a DVBv5 call for DVB-S2 and a DVBv3 call to go back to + * DVB-S, drivers that support both DVB-S and DVB-S2 should have the + * SYS_DVBS entry before the SYS_DVBS2, otherwise it won't switch back + * to DVB-S. + */ +static int dvbv3_set_delivery_system(struct dvb_frontend *fe) +{ + int ncaps; + u32 delsys = SYS_UNDEFINED; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + /* If not set yet, defaults to the first supported delivery system */ + if (c->delivery_system == SYS_UNDEFINED) + c->delivery_system = fe->ops.delsys[0]; + + /* + * Trivial case: just use the current one, if it already a DVBv3 + * delivery system + */ + if (is_dvbv3_delsys(c->delivery_system)) { + dev_dbg(fe->dvb->device, + "%s: Using delivery system to %d\n", + __func__, c->delivery_system); + return 0; + } + + /* + * Seek for the first delivery system that it is compatible with a + * DVBv3 standard + */ + ncaps = 0; + while (ncaps < MAX_DELSYS && fe->ops.delsys[ncaps]) { + if (dvbv3_type(fe->ops.delsys[ncaps]) != DVBV3_UNKNOWN) { + delsys = fe->ops.delsys[ncaps]; + break; + } + ncaps++; + } + if (delsys == SYS_UNDEFINED) { + dev_dbg(fe->dvb->device, + "%s: Couldn't find a delivery system that works with FE_SET_FRONTEND\n", + __func__); + return -EINVAL; + } + return emulate_delivery_system(fe, delsys); +} + +static void prepare_tuning_algo_parameters(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_frontend_tune_settings fetunesettings = { 0 }; + + /* get frontend-specific tuning settings */ + if (fe->ops.get_tune_settings && (fe->ops.get_tune_settings(fe, &fetunesettings) == 0)) { + fepriv->min_delay = (fetunesettings.min_delay_ms * HZ) / 1000; + fepriv->max_drift = fetunesettings.max_drift; + fepriv->step_size = fetunesettings.step_size; + } else { + /* default values */ + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_ISDBS: + case SYS_TURBO: + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + fepriv->min_delay = HZ / 20; + fepriv->step_size = c->symbol_rate / 16000; + fepriv->max_drift = c->symbol_rate / 2000; + break; + case SYS_DVBT: + case SYS_DVBT2: + case SYS_ISDBT: + case SYS_DTMB: + fepriv->min_delay = HZ / 20; + fepriv->step_size = dvb_frontend_get_stepsize(fe) * 2; + fepriv->max_drift = fepriv->step_size + 1; + break; + default: + /* + * FIXME: This sounds wrong! if freqency_stepsize is + * defined by the frontend, why not use it??? + */ + fepriv->min_delay = HZ / 20; + fepriv->step_size = 0; /* no zigzag */ + fepriv->max_drift = 0; + break; + } + } + if (dvb_override_tune_delay > 0) + fepriv->min_delay = (dvb_override_tune_delay * HZ) / 1000; +} + +/** + * dtv_property_process_set - Sets a single DTV property + * @fe: Pointer to &struct dvb_frontend + * @file: Pointer to &struct file + * @cmd: Digital TV command + * @data: An unsigned 32-bits number + * + * This routine assigns the property + * value to the corresponding member of + * &struct dtv_frontend_properties + * + * Returns: + * Zero on success, negative errno on failure. + */ +static int dtv_property_process_set(struct dvb_frontend *fe, + struct file *file, + u32 cmd, u32 data) +{ + int r = 0; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + /** Dump DTV command name and value*/ + if (!cmd || cmd > DTV_MAX_COMMAND) + dev_warn(fe->dvb->device, "%s: SET cmd 0x%08x undefined\n", + __func__, cmd); + else + dev_dbg(fe->dvb->device, + "%s: SET cmd 0x%08x (%s) to 0x%08x\n", + __func__, cmd, dtv_cmd_name(cmd), data); + switch (cmd) { + case DTV_CLEAR: + /* + * Reset a cache of data specific to the frontend here. This does + * not effect hardware. + */ + dvb_frontend_clear_cache(fe); + break; + case DTV_TUNE: + /* + * Use the cached Digital TV properties to tune the + * frontend + */ + dev_dbg(fe->dvb->device, + "%s: Setting the frontend from property cache\n", + __func__); + + r = dtv_set_frontend(fe); + break; + case DTV_FREQUENCY: + c->frequency = data; + break; + case DTV_MODULATION: + c->modulation = data; + break; + case DTV_BANDWIDTH_HZ: + c->bandwidth_hz = data; + break; + case DTV_INVERSION: + c->inversion = data; + break; + case DTV_SYMBOL_RATE: + c->symbol_rate = data; + break; + case DTV_INNER_FEC: + c->fec_inner = data; + break; + case DTV_PILOT: + c->pilot = data; + break; + case DTV_ROLLOFF: + c->rolloff = data; + break; + case DTV_DELIVERY_SYSTEM: + r = dvbv5_set_delivery_system(fe, data); + break; + case DTV_VOLTAGE: + c->voltage = data; + r = dvb_frontend_handle_ioctl(file, FE_SET_VOLTAGE, + (void *)c->voltage); + break; + case DTV_TONE: + c->sectone = data; + r = dvb_frontend_handle_ioctl(file, FE_SET_TONE, + (void *)c->sectone); + break; + case DTV_CODE_RATE_HP: + c->code_rate_HP = data; + break; + case DTV_CODE_RATE_LP: + c->code_rate_LP = data; + break; + case DTV_GUARD_INTERVAL: + c->guard_interval = data; + break; + case DTV_TRANSMISSION_MODE: + c->transmission_mode = data; + break; + case DTV_HIERARCHY: + c->hierarchy = data; + break; + case DTV_INTERLEAVING: + c->interleaving = data; + break; + + /* ISDB-T Support here */ + case DTV_ISDBT_PARTIAL_RECEPTION: + c->isdbt_partial_reception = data; + break; + case DTV_ISDBT_SOUND_BROADCASTING: + c->isdbt_sb_mode = data; + break; + case DTV_ISDBT_SB_SUBCHANNEL_ID: + c->isdbt_sb_subchannel = data; + break; + case DTV_ISDBT_SB_SEGMENT_IDX: + c->isdbt_sb_segment_idx = data; + break; + case DTV_ISDBT_SB_SEGMENT_COUNT: + c->isdbt_sb_segment_count = data; + break; + case DTV_ISDBT_LAYER_ENABLED: + c->isdbt_layer_enabled = data; + break; + case DTV_ISDBT_LAYERA_FEC: + c->layer[0].fec = data; + break; + case DTV_ISDBT_LAYERA_MODULATION: + c->layer[0].modulation = data; + break; + case DTV_ISDBT_LAYERA_SEGMENT_COUNT: + c->layer[0].segment_count = data; + break; + case DTV_ISDBT_LAYERA_TIME_INTERLEAVING: + c->layer[0].interleaving = data; + break; + case DTV_ISDBT_LAYERB_FEC: + c->layer[1].fec = data; + break; + case DTV_ISDBT_LAYERB_MODULATION: + c->layer[1].modulation = data; + break; + case DTV_ISDBT_LAYERB_SEGMENT_COUNT: + c->layer[1].segment_count = data; + break; + case DTV_ISDBT_LAYERB_TIME_INTERLEAVING: + c->layer[1].interleaving = data; + break; + case DTV_ISDBT_LAYERC_FEC: + c->layer[2].fec = data; + break; + case DTV_ISDBT_LAYERC_MODULATION: + c->layer[2].modulation = data; + break; + case DTV_ISDBT_LAYERC_SEGMENT_COUNT: + c->layer[2].segment_count = data; + break; + case DTV_ISDBT_LAYERC_TIME_INTERLEAVING: + c->layer[2].interleaving = data; + break; + + /* Multistream support */ + case DTV_STREAM_ID: + case DTV_DVBT2_PLP_ID_LEGACY: + c->stream_id = data; + break; + + /* Physical layer scrambling support */ + case DTV_SCRAMBLING_SEQUENCE_INDEX: + c->scrambling_sequence_index = data; + break; + + /* ATSC-MH */ + case DTV_ATSCMH_PARADE_ID: + fe->dtv_property_cache.atscmh_parade_id = data; + break; + case DTV_ATSCMH_RS_FRAME_ENSEMBLE: + fe->dtv_property_cache.atscmh_rs_frame_ensemble = data; + break; + + case DTV_LNA: + c->lna = data; + if (fe->ops.set_lna) + r = fe->ops.set_lna(fe); + if (r < 0) + c->lna = LNA_AUTO; + break; + + default: + return -EINVAL; + } + + return r; +} + +static int dvb_frontend_do_ioctl(struct file *file, unsigned int cmd, + void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int err; + + dev_dbg(fe->dvb->device, "%s: (%d)\n", __func__, _IOC_NR(cmd)); + if (down_interruptible(&fepriv->sem)) + return -ERESTARTSYS; + + if (fe->exit != DVB_FE_NO_EXIT) { + up(&fepriv->sem); + return -ENODEV; + } + + /* + * If the frontend is opened in read-only mode, only the ioctls + * that don't interfere with the tune logic should be accepted. + * That allows an external application to monitor the DVB QoS and + * statistics parameters. + * + * That matches all _IOR() ioctls, except for two special cases: + * - FE_GET_EVENT is part of the tuning logic on a DVB application; + * - FE_DISEQC_RECV_SLAVE_REPLY is part of DiSEqC 2.0 + * setup + * So, those two ioctls should also return -EPERM, as otherwise + * reading from them would interfere with a DVB tune application + */ + if ((file->f_flags & O_ACCMODE) == O_RDONLY + && (_IOC_DIR(cmd) != _IOC_READ + || cmd == FE_GET_EVENT + || cmd == FE_DISEQC_RECV_SLAVE_REPLY)) { + up(&fepriv->sem); + return -EPERM; + } + + err = dvb_frontend_handle_ioctl(file, cmd, parg); + + up(&fepriv->sem); + return err; +} + +static long dvb_frontend_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + return dvb_usercopy(file, cmd, arg, dvb_frontend_do_ioctl); +} + +#ifdef CONFIG_COMPAT +struct compat_dtv_property { + __u32 cmd; + __u32 reserved[3]; + union { + __u32 data; + struct dtv_fe_stats st; + struct { + __u8 data[32]; + __u32 len; + __u32 reserved1[3]; + compat_uptr_t reserved2; + } buffer; + } u; + int result; +} __attribute__ ((packed)); + +struct compat_dtv_properties { + __u32 num; + compat_uptr_t props; +}; + +#define COMPAT_FE_SET_PROPERTY _IOW('o', 82, struct compat_dtv_properties) +#define COMPAT_FE_GET_PROPERTY _IOR('o', 83, struct compat_dtv_properties) + +static int dvb_frontend_handle_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int i, err = 0; + + if (cmd == COMPAT_FE_SET_PROPERTY) { + struct compat_dtv_properties prop, *tvps = NULL; + struct compat_dtv_property *tvp = NULL; + + if (copy_from_user(&prop, compat_ptr(arg), sizeof(prop))) + return -EFAULT; + + tvps = ∝ + + /* + * Put an arbitrary limit on the number of messages that can + * be sent at once + */ + if (!tvps->num || (tvps->num > DTV_IOCTL_MAX_MSGS)) + return -EINVAL; + + tvp = memdup_user(compat_ptr(tvps->props), tvps->num * sizeof(*tvp)); + if (IS_ERR(tvp)) + return PTR_ERR(tvp); + + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_set(fe, file, + (tvp + i)->cmd, + (tvp + i)->u.data); + if (err < 0) { + kfree(tvp); + return err; + } + } + kfree(tvp); + } else if (cmd == COMPAT_FE_GET_PROPERTY) { + struct compat_dtv_properties prop, *tvps = NULL; + struct compat_dtv_property *tvp = NULL; + struct dtv_frontend_properties getp = fe->dtv_property_cache; + + if (copy_from_user(&prop, compat_ptr(arg), sizeof(prop))) + return -EFAULT; + + tvps = ∝ + + /* + * Put an arbitrary limit on the number of messages that can + * be sent at once + */ + if (!tvps->num || (tvps->num > DTV_IOCTL_MAX_MSGS)) + return -EINVAL; + + tvp = memdup_user(compat_ptr(tvps->props), tvps->num * sizeof(*tvp)); + if (IS_ERR(tvp)) + return PTR_ERR(tvp); + + /* + * Let's use our own copy of property cache, in order to + * avoid mangling with DTV zigzag logic, as drivers might + * return crap, if they don't check if the data is available + * before updating the properties cache. + */ + if (fepriv->state != FESTATE_IDLE) { + err = dtv_get_frontend(fe, &getp, NULL); + if (err < 0) { + kfree(tvp); + return err; + } + } + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_get( + fe, &getp, (struct dtv_property *)(tvp + i), file); + if (err < 0) { + kfree(tvp); + return err; + } + } + + if (copy_to_user((void __user *)compat_ptr(tvps->props), tvp, + tvps->num * sizeof(struct compat_dtv_property))) { + kfree(tvp); + return -EFAULT; + } + kfree(tvp); + } + + return err; +} + +static long dvb_frontend_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int err; + + if (cmd == COMPAT_FE_SET_PROPERTY || cmd == COMPAT_FE_GET_PROPERTY) { + if (down_interruptible(&fepriv->sem)) + return -ERESTARTSYS; + + err = dvb_frontend_handle_compat_ioctl(file, cmd, arg); + + up(&fepriv->sem); + return err; + } + + return dvb_frontend_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static int dtv_set_frontend(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 rolloff = 0; + + if (dvb_frontend_check_parameters(fe) < 0) + return -EINVAL; + + /* + * Initialize output parameters to match the values given by + * the user. FE_SET_FRONTEND triggers an initial frontend event + * with status = 0, which copies output parameters to userspace. + */ + dtv_property_legacy_params_sync(fe, c, &fepriv->parameters_out); + + /* + * Be sure that the bandwidth will be filled for all + * non-satellite systems, as tuners need to know what + * low pass/Nyquist half filter should be applied, in + * order to avoid inter-channel noise. + * + * ISDB-T and DVB-T/T2 already sets bandwidth. + * ATSC and DVB-C don't set, so, the core should fill it. + * + * On DVB-C Annex A and C, the bandwidth is a function of + * the roll-off and symbol rate. Annex B defines different + * roll-off factors depending on the modulation. Fortunately, + * Annex B is only used with 6MHz, so there's no need to + * calculate it. + * + * While not officially supported, a side effect of handling it at + * the cache level is that a program could retrieve the bandwidth + * via DTV_BANDWIDTH_HZ, which may be useful for test programs. + */ + switch (c->delivery_system) { + case SYS_ATSC: + case SYS_DVBC_ANNEX_B: + c->bandwidth_hz = 6000000; + break; + case SYS_DVBC_ANNEX_A: + rolloff = 115; + break; + case SYS_DVBC_ANNEX_C: + rolloff = 113; + break; + case SYS_DVBS: + case SYS_TURBO: + case SYS_ISDBS: + rolloff = 135; + break; + case SYS_DVBS2: + switch (c->rolloff) { + case ROLLOFF_20: + rolloff = 120; + break; + case ROLLOFF_25: + rolloff = 125; + break; + default: + case ROLLOFF_35: + rolloff = 135; + } + break; + default: + break; + } + if (rolloff) + c->bandwidth_hz = mult_frac(c->symbol_rate, rolloff, 100); + + /* force auto frequency inversion if requested */ + if (dvb_force_auto_inversion) + c->inversion = INVERSION_AUTO; + + /* + * without hierarchical coding code_rate_LP is irrelevant, + * so we tolerate the otherwise invalid FEC_NONE setting + */ + if (c->hierarchy == HIERARCHY_NONE && c->code_rate_LP == FEC_NONE) + c->code_rate_LP = FEC_AUTO; + + prepare_tuning_algo_parameters(fe); + + fepriv->state = FESTATE_RETUNE; + + /* Request the search algorithm to search */ + fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; + + dvb_frontend_clear_events(fe); + dvb_frontend_add_event(fe, 0); + dvb_frontend_wakeup(fe); + fepriv->status = 0; + + return 0; +} + +static int dvb_get_property(struct dvb_frontend *fe, struct file *file, + struct dtv_properties *tvps) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_property *tvp = NULL; + struct dtv_frontend_properties getp; + int i, err; + + memcpy(&getp, &fe->dtv_property_cache, sizeof(getp)); + + dev_dbg(fe->dvb->device, "%s: properties.num = %d\n", + __func__, tvps->num); + dev_dbg(fe->dvb->device, "%s: properties.props = %p\n", + __func__, tvps->props); + + /* + * Put an arbitrary limit on the number of messages that can + * be sent at once + */ + if (!tvps->num || tvps->num > DTV_IOCTL_MAX_MSGS) + return -EINVAL; + + tvp = memdup_user((void __user *)tvps->props, tvps->num * sizeof(*tvp)); + if (IS_ERR(tvp)) + return PTR_ERR(tvp); + + /* + * Let's use our own copy of property cache, in order to + * avoid mangling with DTV zigzag logic, as drivers might + * return crap, if they don't check if the data is available + * before updating the properties cache. + */ + if (fepriv->state != FESTATE_IDLE) { + err = dtv_get_frontend(fe, &getp, NULL); + if (err < 0) + goto out; + } + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_get(fe, &getp, + tvp + i, file); + if (err < 0) + goto out; + } + + if (copy_to_user((void __user *)tvps->props, tvp, + tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + err = 0; +out: + kfree(tvp); + return err; +} + +static int dvb_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p_out) +{ + struct dtv_frontend_properties getp; + + /* + * Let's use our own copy of property cache, in order to + * avoid mangling with DTV zigzag logic, as drivers might + * return crap, if they don't check if the data is available + * before updating the properties cache. + */ + memcpy(&getp, &fe->dtv_property_cache, sizeof(getp)); + + return dtv_get_frontend(fe, &getp, p_out); +} + +static int dvb_frontend_handle_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int i, err = -ENOTSUPP; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + switch (cmd) { + case FE_SET_PROPERTY: { + struct dtv_properties *tvps = parg; + struct dtv_property *tvp = NULL; + + dev_dbg(fe->dvb->device, "%s: properties.num = %d\n", + __func__, tvps->num); + dev_dbg(fe->dvb->device, "%s: properties.props = %p\n", + __func__, tvps->props); + + /* + * Put an arbitrary limit on the number of messages that can + * be sent at once + */ + if (!tvps->num || (tvps->num > DTV_IOCTL_MAX_MSGS)) + return -EINVAL; + + tvp = memdup_user((void __user *)tvps->props, tvps->num * sizeof(*tvp)); + if (IS_ERR(tvp)) + return PTR_ERR(tvp); + + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_set(fe, file, + (tvp + i)->cmd, + (tvp + i)->u.data); + if (err < 0) { + kfree(tvp); + return err; + } + } + kfree(tvp); + err = 0; + break; + } + case FE_GET_PROPERTY: + err = dvb_get_property(fe, file, parg); + break; + + case FE_GET_INFO: { + struct dvb_frontend_info *info = parg; + memset(info, 0, sizeof(*info)); + + strscpy(info->name, fe->ops.info.name, sizeof(info->name)); + info->symbol_rate_min = fe->ops.info.symbol_rate_min; + info->symbol_rate_max = fe->ops.info.symbol_rate_max; + info->symbol_rate_tolerance = fe->ops.info.symbol_rate_tolerance; + info->caps = fe->ops.info.caps; + info->frequency_stepsize = dvb_frontend_get_stepsize(fe); + dvb_frontend_get_frequency_limits(fe, &info->frequency_min, + &info->frequency_max, + &info->frequency_tolerance); + + /* + * Associate the 4 delivery systems supported by DVBv3 + * API with their DVBv5 counterpart. For the other standards, + * use the closest type, assuming that it would hopefully + * work with a DVBv3 application. + * It should be noticed that, on multi-frontend devices with + * different types (terrestrial and cable, for example), + * a pure DVBv3 application won't be able to use all delivery + * systems. Yet, changing the DVBv5 cache to the other delivery + * system should be enough for making it work. + */ + switch (dvbv3_type(c->delivery_system)) { + case DVBV3_QPSK: + info->type = FE_QPSK; + break; + case DVBV3_ATSC: + info->type = FE_ATSC; + break; + case DVBV3_QAM: + info->type = FE_QAM; + break; + case DVBV3_OFDM: + info->type = FE_OFDM; + break; + default: + dev_err(fe->dvb->device, + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + info->type = FE_OFDM; + } + dev_dbg(fe->dvb->device, "%s: current delivery system on cache: %d, V3 type: %d\n", + __func__, c->delivery_system, info->type); + + /* Set CAN_INVERSION_AUTO bit on in other than oneshot mode */ + if (!(fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT)) + info->caps |= FE_CAN_INVERSION_AUTO; + err = 0; + break; + } + + case FE_READ_STATUS: { + enum fe_status *status = parg; + + /* if retune was requested but hasn't occurred yet, prevent + * that user get signal state from previous tuning */ + if (fepriv->state == FESTATE_RETUNE || + fepriv->state == FESTATE_ERROR) { + err = 0; + *status = 0; + break; + } + + if (fe->ops.read_status) + err = fe->ops.read_status(fe, status); + break; + } + + case FE_DISEQC_RESET_OVERLOAD: + if (fe->ops.diseqc_reset_overload) { + err = fe->ops.diseqc_reset_overload(fe); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_SEND_MASTER_CMD: + if (fe->ops.diseqc_send_master_cmd) { + struct dvb_diseqc_master_cmd *cmd = parg; + + if (cmd->msg_len > sizeof(cmd->msg)) { + err = -EINVAL; + break; + } + err = fe->ops.diseqc_send_master_cmd(fe, cmd); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_SEND_BURST: + if (fe->ops.diseqc_send_burst) { + err = fe->ops.diseqc_send_burst(fe, (long)parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_TONE: + if (fe->ops.set_tone) { + fepriv->tone = (long)parg; + err = fe->ops.set_tone(fe, fepriv->tone); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_VOLTAGE: + if (fe->ops.set_voltage) { + fepriv->voltage = (long)parg; + err = fe->ops.set_voltage(fe, fepriv->voltage); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_RECV_SLAVE_REPLY: + if (fe->ops.diseqc_recv_slave_reply) + err = fe->ops.diseqc_recv_slave_reply(fe, parg); + break; + + case FE_ENABLE_HIGH_LNB_VOLTAGE: + if (fe->ops.enable_high_lnb_voltage) + err = fe->ops.enable_high_lnb_voltage(fe, (long)parg); + break; + + case FE_SET_FRONTEND_TUNE_MODE: + fepriv->tune_mode_flags = (unsigned long)parg; + err = 0; + break; + /* DEPRECATED dish control ioctls */ + + case FE_DISHNETWORK_SEND_LEGACY_CMD: + if (fe->ops.dishnetwork_send_legacy_command) { + err = fe->ops.dishnetwork_send_legacy_command(fe, + (unsigned long)parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } else if (fe->ops.set_voltage) { + /* + * NOTE: This is a fallback condition. Some frontends + * (stv0299 for instance) take longer than 8msec to + * respond to a set_voltage command. Those switches + * need custom routines to switch properly. For all + * other frontends, the following should work ok. + * Dish network legacy switches (as used by Dish500) + * are controlled by sending 9-bit command words + * spaced 8msec apart. + * the actual command word is switch/port dependent + * so it is up to the userspace application to send + * the right command. + * The command must always start with a '0' after + * initialization, so parg is 8 bits and does not + * include the initialization or start bit + */ + unsigned long swcmd = ((unsigned long)parg) << 1; + ktime_t nexttime; + ktime_t tv[10]; + int i; + u8 last = 1; + + if (dvb_frontend_debug) + dprintk("switch command: 0x%04lx\n", + swcmd); + nexttime = ktime_get_boottime(); + if (dvb_frontend_debug) + tv[0] = nexttime; + /* before sending a command, initialize by sending + * a 32ms 18V to the switch + */ + fe->ops.set_voltage(fe, SEC_VOLTAGE_18); + dvb_frontend_sleep_until(&nexttime, 32000); + + for (i = 0; i < 9; i++) { + if (dvb_frontend_debug) + tv[i + 1] = ktime_get_boottime(); + if ((swcmd & 0x01) != last) { + /* set voltage to (last ? 13V : 18V) */ + fe->ops.set_voltage(fe, (last) ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18); + last = (last) ? 0 : 1; + } + swcmd = swcmd >> 1; + if (i != 8) + dvb_frontend_sleep_until(&nexttime, 8000); + } + if (dvb_frontend_debug) { + dprintk("(adapter %d): switch delay (should be 32k followed by all 8k)\n", + fe->dvb->num); + for (i = 1; i < 10; i++) + pr_info("%d: %d\n", i, + (int)ktime_us_delta(tv[i], tv[i - 1])); + } + err = 0; + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + /* DEPRECATED statistics ioctls */ + + case FE_READ_BER: + if (fe->ops.read_ber) { + if (fepriv->thread) + err = fe->ops.read_ber(fe, parg); + else + err = -EAGAIN; + } + break; + + case FE_READ_SIGNAL_STRENGTH: + if (fe->ops.read_signal_strength) { + if (fepriv->thread) + err = fe->ops.read_signal_strength(fe, parg); + else + err = -EAGAIN; + } + break; + + case FE_READ_SNR: + if (fe->ops.read_snr) { + if (fepriv->thread) + err = fe->ops.read_snr(fe, parg); + else + err = -EAGAIN; + } + break; + + case FE_READ_UNCORRECTED_BLOCKS: + if (fe->ops.read_ucblocks) { + if (fepriv->thread) + err = fe->ops.read_ucblocks(fe, parg); + else + err = -EAGAIN; + } + break; + + /* DEPRECATED DVBv3 ioctls */ + + case FE_SET_FRONTEND: + err = dvbv3_set_delivery_system(fe); + if (err) + break; + + err = dtv_property_cache_sync(fe, c, parg); + if (err) + break; + err = dtv_set_frontend(fe); + break; + + case FE_GET_EVENT: + err = dvb_frontend_get_event(fe, parg, file->f_flags); + break; + + case FE_GET_FRONTEND: + err = dvb_get_frontend(fe, parg); + break; + + default: + return -ENOTSUPP; + } /* switch */ + + return err; +} + +static __poll_t dvb_frontend_poll(struct file *file, struct poll_table_struct *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + dev_dbg_ratelimited(fe->dvb->device, "%s:\n", __func__); + + poll_wait(file, &fepriv->events.wait_queue, wait); + + if (fepriv->events.eventw != fepriv->events.eventr) + return (EPOLLIN | EPOLLRDNORM | EPOLLPRI); + + return 0; +} + +static int dvb_frontend_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_adapter *adapter = fe->dvb; + int ret; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + if (fe->exit == DVB_FE_DEVICE_REMOVED) + return -ENODEV; + + if (adapter->mfe_shared) { + mutex_lock(&adapter->mfe_lock); + + if (!adapter->mfe_dvbdev) + adapter->mfe_dvbdev = dvbdev; + + else if (adapter->mfe_dvbdev != dvbdev) { + struct dvb_device + *mfedev = adapter->mfe_dvbdev; + struct dvb_frontend + *mfe = mfedev->priv; + struct dvb_frontend_private + *mfepriv = mfe->frontend_priv; + int mferetry = (dvb_mfe_wait_time << 1); + + mutex_unlock(&adapter->mfe_lock); + while (mferetry-- && (mfedev->users != -1 || + mfepriv->thread)) { + if (msleep_interruptible(500)) { + if (signal_pending(current)) + return -EINTR; + } + } + + mutex_lock(&adapter->mfe_lock); + if (adapter->mfe_dvbdev != dvbdev) { + mfedev = adapter->mfe_dvbdev; + mfe = mfedev->priv; + mfepriv = mfe->frontend_priv; + if (mfedev->users != -1 || + mfepriv->thread) { + mutex_unlock(&adapter->mfe_lock); + return -EBUSY; + } + adapter->mfe_dvbdev = dvbdev; + } + } + } + + if (dvbdev->users == -1 && fe->ops.ts_bus_ctrl) { + if ((ret = fe->ops.ts_bus_ctrl(fe, 1)) < 0) + goto err0; + + /* If we took control of the bus, we need to force + reinitialization. This is because many ts_bus_ctrl() + functions strobe the RESET pin on the demod, and if the + frontend thread already exists then the dvb_init() routine + won't get called (which is what usually does initial + register configuration). */ + fepriv->reinitialise = 1; + } + + if ((ret = dvb_generic_open(inode, file)) < 0) + goto err1; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + /* normal tune mode when opened R/W */ + fepriv->tune_mode_flags &= ~FE_TUNE_MODE_ONESHOT; + fepriv->tone = -1; + fepriv->voltage = -1; + +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + mutex_lock(&fe->dvb->mdev_lock); + if (fe->dvb->mdev) { + mutex_lock(&fe->dvb->mdev->graph_mutex); + if (fe->dvb->mdev->enable_source) + ret = fe->dvb->mdev->enable_source( + dvbdev->entity, + &fepriv->pipe); + mutex_unlock(&fe->dvb->mdev->graph_mutex); + if (ret) { + mutex_unlock(&fe->dvb->mdev_lock); + dev_err(fe->dvb->device, + "Tuner is busy. Error %d\n", ret); + goto err2; + } + } + mutex_unlock(&fe->dvb->mdev_lock); +#endif + ret = dvb_frontend_start(fe); + if (ret) + goto err3; + + /* empty event queue */ + fepriv->events.eventr = fepriv->events.eventw = 0; + } + + dvb_frontend_get(fe); + + if (adapter->mfe_shared) + mutex_unlock(&adapter->mfe_lock); + return ret; + +err3: +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + mutex_lock(&fe->dvb->mdev_lock); + if (fe->dvb->mdev) { + mutex_lock(&fe->dvb->mdev->graph_mutex); + if (fe->dvb->mdev->disable_source) + fe->dvb->mdev->disable_source(dvbdev->entity); + mutex_unlock(&fe->dvb->mdev->graph_mutex); + } + mutex_unlock(&fe->dvb->mdev_lock); +err2: +#endif + dvb_generic_release(inode, file); +err1: + if (dvbdev->users == -1 && fe->ops.ts_bus_ctrl) + fe->ops.ts_bus_ctrl(fe, 0); +err0: + if (adapter->mfe_shared) + mutex_unlock(&adapter->mfe_lock); + return ret; +} + +static int dvb_frontend_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int ret; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + fepriv->release_jiffies = jiffies; + mb(); + } + + ret = dvb_generic_release(inode, file); + + if (dvbdev->users == -1) { + wake_up(&fepriv->wait_queue); +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + mutex_lock(&fe->dvb->mdev_lock); + if (fe->dvb->mdev) { + mutex_lock(&fe->dvb->mdev->graph_mutex); + if (fe->dvb->mdev->disable_source) + fe->dvb->mdev->disable_source(dvbdev->entity); + mutex_unlock(&fe->dvb->mdev->graph_mutex); + } + mutex_unlock(&fe->dvb->mdev_lock); +#endif + if (fe->exit != DVB_FE_NO_EXIT) + wake_up(&dvbdev->wait_queue); + if (fe->ops.ts_bus_ctrl) + fe->ops.ts_bus_ctrl(fe, 0); + } + + dvb_frontend_put(fe); + + return ret; +} + +static const struct file_operations dvb_frontend_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dvb_frontend_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = dvb_frontend_compat_ioctl, +#endif + .poll = dvb_frontend_poll, + .open = dvb_frontend_open, + .release = dvb_frontend_release, + .llseek = noop_llseek, +}; + +int dvb_frontend_suspend(struct dvb_frontend *fe) +{ + int ret = 0; + + dev_dbg(fe->dvb->device, "%s: adap=%d fe=%d\n", __func__, fe->dvb->num, + fe->id); + + if (fe->ops.tuner_ops.suspend) + ret = fe->ops.tuner_ops.suspend(fe); + else if (fe->ops.tuner_ops.sleep) + ret = fe->ops.tuner_ops.sleep(fe); + + if (fe->ops.suspend) + ret = fe->ops.suspend(fe); + else if (fe->ops.sleep) + ret = fe->ops.sleep(fe); + + return ret; +} +EXPORT_SYMBOL(dvb_frontend_suspend); + +int dvb_frontend_resume(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int ret = 0; + + dev_dbg(fe->dvb->device, "%s: adap=%d fe=%d\n", __func__, fe->dvb->num, + fe->id); + + fe->exit = DVB_FE_DEVICE_RESUME; + if (fe->ops.resume) + ret = fe->ops.resume(fe); + else if (fe->ops.init) + ret = fe->ops.init(fe); + + if (fe->ops.tuner_ops.resume) + ret = fe->ops.tuner_ops.resume(fe); + else if (fe->ops.tuner_ops.init) + ret = fe->ops.tuner_ops.init(fe); + + if (fe->ops.set_tone && fepriv->tone != -1) + fe->ops.set_tone(fe, fepriv->tone); + if (fe->ops.set_voltage && fepriv->voltage != -1) + fe->ops.set_voltage(fe, fepriv->voltage); + + fe->exit = DVB_FE_NO_EXIT; + fepriv->state = FESTATE_RETUNE; + dvb_frontend_wakeup(fe); + + return ret; +} +EXPORT_SYMBOL(dvb_frontend_resume); + +int dvb_register_frontend(struct dvb_adapter *dvb, + struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv; + const struct dvb_device dvbdev_template = { + .users = ~0, + .writers = 1, + .readers = (~0) - 1, + .fops = &dvb_frontend_fops, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = fe->ops.info.name, +#endif + }; + int ret; + + dev_dbg(dvb->device, "%s:\n", __func__); + + if (mutex_lock_interruptible(&frontend_mutex)) + return -ERESTARTSYS; + + fe->frontend_priv = kzalloc(sizeof(struct dvb_frontend_private), GFP_KERNEL); + if (!fe->frontend_priv) { + mutex_unlock(&frontend_mutex); + return -ENOMEM; + } + fepriv = fe->frontend_priv; + + kref_init(&fe->refcount); + + /* + * After initialization, there need to be two references: one + * for dvb_unregister_frontend(), and another one for + * dvb_frontend_detach(). + */ + dvb_frontend_get(fe); + + sema_init(&fepriv->sem, 1); + init_waitqueue_head(&fepriv->wait_queue); + init_waitqueue_head(&fepriv->events.wait_queue); + mutex_init(&fepriv->events.mtx); + fe->dvb = dvb; + fepriv->inversion = INVERSION_OFF; + + dev_info(fe->dvb->device, + "DVB: registering adapter %i frontend %i (%s)...\n", + fe->dvb->num, fe->id, fe->ops.info.name); + + ret = dvb_register_device(fe->dvb, &fepriv->dvbdev, &dvbdev_template, + fe, DVB_DEVICE_FRONTEND, 0); + if (ret) { + dvb_frontend_put(fe); + mutex_unlock(&frontend_mutex); + return ret; + } + + /* + * Initialize the cache to the proper values according with the + * first supported delivery system (ops->delsys[0]) + */ + + fe->dtv_property_cache.delivery_system = fe->ops.delsys[0]; + dvb_frontend_clear_cache(fe); + + mutex_unlock(&frontend_mutex); + return 0; +} +EXPORT_SYMBOL(dvb_register_frontend); + +int dvb_unregister_frontend(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + mutex_lock(&frontend_mutex); + dvb_frontend_stop(fe); + dvb_remove_device(fepriv->dvbdev); + + /* fe is invalid now */ + mutex_unlock(&frontend_mutex); + dvb_frontend_put(fe); + return 0; +} +EXPORT_SYMBOL(dvb_unregister_frontend); + +static void dvb_frontend_invoke_release(struct dvb_frontend *fe, + void (*release)(struct dvb_frontend *fe)) +{ + if (release) { + release(fe); +#ifdef CONFIG_MEDIA_ATTACH + dvb_detach(release); +#endif + } +} + +void dvb_frontend_detach(struct dvb_frontend *fe) +{ + dvb_frontend_invoke_release(fe, fe->ops.release_sec); + dvb_frontend_invoke_release(fe, fe->ops.tuner_ops.release); + dvb_frontend_invoke_release(fe, fe->ops.analog_ops.release); + dvb_frontend_put(fe); +} +EXPORT_SYMBOL(dvb_frontend_detach); diff --git a/drivers/media/dvb-core/dvb_math.c b/drivers/media/dvb-core/dvb_math.c new file mode 100644 index 000000000..dc90564d7 --- /dev/null +++ b/drivers/media/dvb-core/dvb_math.c @@ -0,0 +1,141 @@ +/* + * dvb-math provides some complex fixed-point math + * operations shared between the dvb related stuff + * + * Copyright (C) 2006 Christoph Pfister (christophpfister@gmail.com) + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/bug.h> +#include <media/dvb_math.h> + +static const unsigned short logtable[256] = { + 0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7, + 0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508, + 0x1664, 0x17bf, 0x1919, 0x1a71, 0x1bc8, 0x1d1e, 0x1e73, 0x1fc6, + 0x2119, 0x226a, 0x23ba, 0x2508, 0x2656, 0x27a2, 0x28ed, 0x2a37, + 0x2b80, 0x2cc8, 0x2e0f, 0x2f54, 0x3098, 0x31dc, 0x331e, 0x345f, + 0x359f, 0x36de, 0x381b, 0x3958, 0x3a94, 0x3bce, 0x3d08, 0x3e41, + 0x3f78, 0x40af, 0x41e4, 0x4319, 0x444c, 0x457f, 0x46b0, 0x47e1, + 0x4910, 0x4a3f, 0x4b6c, 0x4c99, 0x4dc5, 0x4eef, 0x5019, 0x5142, + 0x526a, 0x5391, 0x54b7, 0x55dc, 0x5700, 0x5824, 0x5946, 0x5a68, + 0x5b89, 0x5ca8, 0x5dc7, 0x5ee5, 0x6003, 0x611f, 0x623a, 0x6355, + 0x646f, 0x6588, 0x66a0, 0x67b7, 0x68ce, 0x69e4, 0x6af8, 0x6c0c, + 0x6d20, 0x6e32, 0x6f44, 0x7055, 0x7165, 0x7274, 0x7383, 0x7490, + 0x759d, 0x76aa, 0x77b5, 0x78c0, 0x79ca, 0x7ad3, 0x7bdb, 0x7ce3, + 0x7dea, 0x7ef0, 0x7ff6, 0x80fb, 0x81ff, 0x8302, 0x8405, 0x8507, + 0x8608, 0x8709, 0x8809, 0x8908, 0x8a06, 0x8b04, 0x8c01, 0x8cfe, + 0x8dfa, 0x8ef5, 0x8fef, 0x90e9, 0x91e2, 0x92db, 0x93d2, 0x94ca, + 0x95c0, 0x96b6, 0x97ab, 0x98a0, 0x9994, 0x9a87, 0x9b7a, 0x9c6c, + 0x9d5e, 0x9e4f, 0x9f3f, 0xa02e, 0xa11e, 0xa20c, 0xa2fa, 0xa3e7, + 0xa4d4, 0xa5c0, 0xa6ab, 0xa796, 0xa881, 0xa96a, 0xaa53, 0xab3c, + 0xac24, 0xad0c, 0xadf2, 0xaed9, 0xafbe, 0xb0a4, 0xb188, 0xb26c, + 0xb350, 0xb433, 0xb515, 0xb5f7, 0xb6d9, 0xb7ba, 0xb89a, 0xb97a, + 0xba59, 0xbb38, 0xbc16, 0xbcf4, 0xbdd1, 0xbead, 0xbf8a, 0xc065, + 0xc140, 0xc21b, 0xc2f5, 0xc3cf, 0xc4a8, 0xc580, 0xc658, 0xc730, + 0xc807, 0xc8de, 0xc9b4, 0xca8a, 0xcb5f, 0xcc34, 0xcd08, 0xcddc, + 0xceaf, 0xcf82, 0xd054, 0xd126, 0xd1f7, 0xd2c8, 0xd399, 0xd469, + 0xd538, 0xd607, 0xd6d6, 0xd7a4, 0xd872, 0xd93f, 0xda0c, 0xdad9, + 0xdba5, 0xdc70, 0xdd3b, 0xde06, 0xded0, 0xdf9a, 0xe063, 0xe12c, + 0xe1f5, 0xe2bd, 0xe385, 0xe44c, 0xe513, 0xe5d9, 0xe69f, 0xe765, + 0xe82a, 0xe8ef, 0xe9b3, 0xea77, 0xeb3b, 0xebfe, 0xecc1, 0xed83, + 0xee45, 0xef06, 0xefc8, 0xf088, 0xf149, 0xf209, 0xf2c8, 0xf387, + 0xf446, 0xf505, 0xf5c3, 0xf680, 0xf73e, 0xf7fb, 0xf8b7, 0xf973, + 0xfa2f, 0xfaea, 0xfba5, 0xfc60, 0xfd1a, 0xfdd4, 0xfe8e, 0xff47 +}; + +unsigned int intlog2(u32 value) +{ + /** + * returns: log2(value) * 2^24 + * wrong result if value = 0 (log2(0) is undefined) + */ + unsigned int msb; + unsigned int logentry; + unsigned int significand; + unsigned int interpolation; + + if (unlikely(value == 0)) { + WARN_ON(1); + return 0; + } + + /* first detect the msb (count begins at 0) */ + msb = fls(value) - 1; + + /** + * now we use a logtable after the following method: + * + * log2(2^x * y) * 2^24 = x * 2^24 + log2(y) * 2^24 + * where x = msb and therefore 1 <= y < 2 + * first y is determined by shifting the value left + * so that msb is bit 31 + * 0x00231f56 -> 0x8C7D5800 + * the result is y * 2^31 -> "significand" + * then the highest 9 bits are used for a table lookup + * the highest bit is discarded because it's always set + * the highest nine bits in our example are 100011000 + * so we would use the entry 0x18 + */ + significand = value << (31 - msb); + logentry = (significand >> 23) & 0xff; + + /** + * last step we do is interpolation because of the + * limitations of the log table the error is that part of + * the significand which isn't used for lookup then we + * compute the ratio between the error and the next table entry + * and interpolate it between the log table entry used and the + * next one the biggest error possible is 0x7fffff + * (in our example it's 0x7D5800) + * needed value for next table entry is 0x800000 + * so the interpolation is + * (error / 0x800000) * (logtable_next - logtable_current) + * in the implementation the division is moved to the end for + * better accuracy there is also an overflow correction if + * logtable_next is 256 + */ + interpolation = ((significand & 0x7fffff) * + ((logtable[(logentry + 1) & 0xff] - + logtable[logentry]) & 0xffff)) >> 15; + + /* now we return the result */ + return ((msb << 24) + (logtable[logentry] << 8) + interpolation); +} +EXPORT_SYMBOL(intlog2); + +unsigned int intlog10(u32 value) +{ + /** + * returns: log10(value) * 2^24 + * wrong result if value = 0 (log10(0) is undefined) + */ + u64 log; + + if (unlikely(value == 0)) { + WARN_ON(1); + return 0; + } + + log = intlog2(value); + + /** + * we use the following method: + * log10(x) = log2(x) * log10(2) + */ + + return (log * 646456993) >> 31; +} +EXPORT_SYMBOL(intlog10); diff --git a/drivers/media/dvb-core/dvb_net.c b/drivers/media/dvb-core/dvb_net.c new file mode 100644 index 000000000..8bb8dd34c --- /dev/null +++ b/drivers/media/dvb-core/dvb_net.c @@ -0,0 +1,1664 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * dvb_net.c + * + * Copyright (C) 2001 Convergence integrated media GmbH + * Ralph Metzler <ralph@convergence.de> + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * ULE Decapsulation code: + * Copyright (C) 2003, 2004 gcs - Global Communication & Services GmbH. + * and Department of Scientific Computing + * Paris Lodron University of Salzburg. + * Hilmar Linder <hlinder@cosy.sbg.ac.at> + * and Wolfram Stering <wstering@cosy.sbg.ac.at> + * + * ULE Decaps according to RFC 4326. + */ + +/* + * ULE ChangeLog: + * Feb 2004: hl/ws v1: Implementing draft-fair-ipdvb-ule-01.txt + * + * Dec 2004: hl/ws v2: Implementing draft-ietf-ipdvb-ule-03.txt: + * ULE Extension header handling. + * Bugreports by Moritz Vieth and Hanno Tersteegen, + * Fraunhofer Institute for Open Communication Systems + * Competence Center for Advanced Satellite Communications. + * Bugfixes and robustness improvements. + * Filtering on dest MAC addresses, if present (D-Bit = 0) + * DVB_ULE_DEBUG compile-time option. + * Apr 2006: cp v3: Bugfixes and compliency with RFC 4326 (ULE) by + * Christian Praehauser <cpraehaus@cosy.sbg.ac.at>, + * Paris Lodron University of Salzburg. + */ + +/* + * FIXME / TODO (dvb_net.c): + * + * Unloading does not work for 2.6.9 kernels: a refcount doesn't go to zero. + * + */ + +#define pr_fmt(fmt) "dvb_net: " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/nospec.h> +#include <linux/etherdevice.h> +#include <linux/dvb/net.h> +#include <linux/uio.h> +#include <linux/uaccess.h> +#include <linux/crc32.h> +#include <linux/mutex.h> +#include <linux/sched.h> + +#include <media/dvb_demux.h> +#include <media/dvb_net.h> + +static inline __u32 iov_crc32( __u32 c, struct kvec *iov, unsigned int cnt ) +{ + unsigned int j; + for (j = 0; j < cnt; j++) + c = crc32_be( c, iov[j].iov_base, iov[j].iov_len ); + return c; +} + + +#define DVB_NET_MULTICAST_MAX 10 + +#ifdef DVB_ULE_DEBUG +/* + * The code inside DVB_ULE_DEBUG keeps a history of the + * last 100 TS cells processed. + */ +static unsigned char ule_hist[100*TS_SZ] = { 0 }; +static unsigned char *ule_where = ule_hist, ule_dump; + +static void hexdump(const unsigned char *buf, unsigned short len) +{ + print_hex_dump_debug("", DUMP_PREFIX_OFFSET, 16, 1, buf, len, true); +} +#endif + +struct dvb_net_priv { + int in_use; + u16 pid; + struct net_device *net; + struct dvb_net *host; + struct dmx_demux *demux; + struct dmx_section_feed *secfeed; + struct dmx_section_filter *secfilter; + struct dmx_ts_feed *tsfeed; + int multi_num; + struct dmx_section_filter *multi_secfilter[DVB_NET_MULTICAST_MAX]; + unsigned char multi_macs[DVB_NET_MULTICAST_MAX][6]; + int rx_mode; +#define RX_MODE_UNI 0 +#define RX_MODE_MULTI 1 +#define RX_MODE_ALL_MULTI 2 +#define RX_MODE_PROMISC 3 + struct work_struct set_multicast_list_wq; + struct work_struct restart_net_feed_wq; + unsigned char feedtype; /* Either FEED_TYPE_ or FEED_TYPE_ULE */ + int need_pusi; /* Set to 1, if synchronization on PUSI required. */ + unsigned char tscc; /* TS continuity counter after sync on PUSI. */ + struct sk_buff *ule_skb; /* ULE SNDU decodes into this buffer. */ + unsigned char *ule_next_hdr; /* Pointer into skb to next ULE extension header. */ + unsigned short ule_sndu_len; /* ULE SNDU length in bytes, w/o D-Bit. */ + unsigned short ule_sndu_type; /* ULE SNDU type field, complete. */ + unsigned char ule_sndu_type_1; /* ULE SNDU type field, if split across 2 TS cells. */ + unsigned char ule_dbit; /* Whether the DestMAC address present + * or not (bit is set). */ + unsigned char ule_bridged; /* Whether the ULE_BRIDGED extension header was found. */ + int ule_sndu_remain; /* Nr. of bytes still required for current ULE SNDU. */ + unsigned long ts_count; /* Current ts cell counter. */ + struct mutex mutex; +}; + + +/* + * Determine the packet's protocol ID. The rule here is that we + * assume 802.3 if the type field is short enough to be a length. + * This is normal practice and works for any 'now in use' protocol. + * + * stolen from eth.c out of the linux kernel, hacked for dvb-device + * by Michael Holzt <kju@debian.org> + */ +static __be16 dvb_net_eth_type_trans(struct sk_buff *skb, + struct net_device *dev) +{ + struct ethhdr *eth; + unsigned char *rawp; + + skb_reset_mac_header(skb); + skb_pull(skb,dev->hard_header_len); + eth = eth_hdr(skb); + + if (*eth->h_dest & 1) { + if(ether_addr_equal(eth->h_dest,dev->broadcast)) + skb->pkt_type=PACKET_BROADCAST; + else + skb->pkt_type=PACKET_MULTICAST; + } + + if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) + return eth->h_proto; + + rawp = skb->data; + + /* + * This is a magic hack to spot IPX packets. Older Novell breaks + * the protocol design and runs IPX over 802.3 without an 802.2 LLC + * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This + * won't work for fault tolerant netware but does for the rest. + */ + if (*(unsigned short *)rawp == 0xFFFF) + return htons(ETH_P_802_3); + + /* + * Real 802.2 LLC + */ + return htons(ETH_P_802_2); +} + +#define TS_SZ 188 +#define TS_SYNC 0x47 +#define TS_TEI 0x80 +#define TS_SC 0xC0 +#define TS_PUSI 0x40 +#define TS_AF_A 0x20 +#define TS_AF_D 0x10 + +/* ULE Extension Header handlers. */ + +#define ULE_TEST 0 +#define ULE_BRIDGED 1 + +#define ULE_OPTEXTHDR_PADDING 0 + +static int ule_test_sndu( struct dvb_net_priv *p ) +{ + return -1; +} + +static int ule_bridged_sndu( struct dvb_net_priv *p ) +{ + struct ethhdr *hdr = (struct ethhdr*) p->ule_next_hdr; + if(ntohs(hdr->h_proto) < ETH_P_802_3_MIN) { + int framelen = p->ule_sndu_len - ((p->ule_next_hdr+sizeof(struct ethhdr)) - p->ule_skb->data); + /* A frame Type < ETH_P_802_3_MIN for a bridged frame, introduces a LLC Length field. */ + if(framelen != ntohs(hdr->h_proto)) { + return -1; + } + } + /* Note: + * From RFC4326: + * "A bridged SNDU is a Mandatory Extension Header of Type 1. + * It must be the final (or only) extension header specified in the header chain of a SNDU." + * The 'ule_bridged' flag will cause the extension header processing loop to terminate. + */ + p->ule_bridged = 1; + return 0; +} + +static int ule_exthdr_padding(struct dvb_net_priv *p) +{ + return 0; +} + +/* + * Handle ULE extension headers. + * Function is called after a successful CRC32 verification of an ULE SNDU to complete its decoding. + * Returns: >= 0: nr. of bytes consumed by next extension header + * -1: Mandatory extension header that is not recognized or TEST SNDU; discard. + */ +static int handle_one_ule_extension( struct dvb_net_priv *p ) +{ + /* Table of mandatory extension header handlers. The header type is the index. */ + static int (*ule_mandatory_ext_handlers[255])( struct dvb_net_priv *p ) = + { [0] = ule_test_sndu, [1] = ule_bridged_sndu, [2] = NULL, }; + + /* Table of optional extension header handlers. The header type is the index. */ + static int (*ule_optional_ext_handlers[255])( struct dvb_net_priv *p ) = + { [0] = ule_exthdr_padding, [1] = NULL, }; + + int ext_len = 0; + unsigned char hlen = (p->ule_sndu_type & 0x0700) >> 8; + unsigned char htype = p->ule_sndu_type & 0x00FF; + + /* Discriminate mandatory and optional extension headers. */ + if (hlen == 0) { + /* Mandatory extension header */ + if (ule_mandatory_ext_handlers[htype]) { + ext_len = ule_mandatory_ext_handlers[htype]( p ); + if(ext_len >= 0) { + p->ule_next_hdr += ext_len; + if (!p->ule_bridged) { + p->ule_sndu_type = ntohs(*(__be16 *)p->ule_next_hdr); + p->ule_next_hdr += 2; + } else { + p->ule_sndu_type = ntohs(*(__be16 *)(p->ule_next_hdr + ((p->ule_dbit ? 2 : 3) * ETH_ALEN))); + /* This assures the extension handling loop will terminate. */ + } + } + // else: extension handler failed or SNDU should be discarded + } else + ext_len = -1; /* SNDU has to be discarded. */ + } else { + /* Optional extension header. Calculate the length. */ + ext_len = hlen << 1; + /* Process the optional extension header according to its type. */ + if (ule_optional_ext_handlers[htype]) + (void)ule_optional_ext_handlers[htype]( p ); + p->ule_next_hdr += ext_len; + p->ule_sndu_type = ntohs( *(__be16 *)(p->ule_next_hdr-2) ); + /* + * note: the length of the next header type is included in the + * length of THIS optional extension header + */ + } + + return ext_len; +} + +static int handle_ule_extensions( struct dvb_net_priv *p ) +{ + int total_ext_len = 0, l; + + p->ule_next_hdr = p->ule_skb->data; + do { + l = handle_one_ule_extension( p ); + if (l < 0) + return l; /* Stop extension header processing and discard SNDU. */ + total_ext_len += l; + pr_debug("ule_next_hdr=%p, ule_sndu_type=%i, l=%i, total_ext_len=%i\n", + p->ule_next_hdr, (int)p->ule_sndu_type, + l, total_ext_len); + + } while (p->ule_sndu_type < ETH_P_802_3_MIN); + + return total_ext_len; +} + + +/* Prepare for a new ULE SNDU: reset the decoder state. */ +static inline void reset_ule( struct dvb_net_priv *p ) +{ + p->ule_skb = NULL; + p->ule_next_hdr = NULL; + p->ule_sndu_len = 0; + p->ule_sndu_type = 0; + p->ule_sndu_type_1 = 0; + p->ule_sndu_remain = 0; + p->ule_dbit = 0xFF; + p->ule_bridged = 0; +} + +/* + * Decode ULE SNDUs according to draft-ietf-ipdvb-ule-03.txt from a sequence of + * TS cells of a single PID. + */ + +struct dvb_net_ule_handle { + struct net_device *dev; + struct dvb_net_priv *priv; + struct ethhdr *ethh; + const u8 *buf; + size_t buf_len; + unsigned long skipped; + const u8 *ts, *ts_end, *from_where; + u8 ts_remain, how_much, new_ts; + bool error; +}; + +static int dvb_net_ule_new_ts_cell(struct dvb_net_ule_handle *h) +{ + /* We are about to process a new TS cell. */ + +#ifdef DVB_ULE_DEBUG + if (ule_where >= &ule_hist[100*TS_SZ]) + ule_where = ule_hist; + memcpy(ule_where, h->ts, TS_SZ); + if (ule_dump) { + hexdump(ule_where, TS_SZ); + ule_dump = 0; + } + ule_where += TS_SZ; +#endif + + /* + * Check TS h->error conditions: sync_byte, transport_error_indicator, + * scrambling_control . + */ + if ((h->ts[0] != TS_SYNC) || (h->ts[1] & TS_TEI) || + ((h->ts[3] & TS_SC) != 0)) { + pr_warn("%lu: Invalid TS cell: SYNC %#x, TEI %u, SC %#x.\n", + h->priv->ts_count, h->ts[0], + (h->ts[1] & TS_TEI) >> 7, + (h->ts[3] & TS_SC) >> 6); + + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (h->priv->ule_skb) { + dev_kfree_skb(h->priv->ule_skb); + /* Prepare for next SNDU. */ + h->dev->stats.rx_errors++; + h->dev->stats.rx_frame_errors++; + } + reset_ule(h->priv); + h->priv->need_pusi = 1; + + /* Continue with next TS cell. */ + h->ts += TS_SZ; + h->priv->ts_count++; + return 1; + } + + h->ts_remain = 184; + h->from_where = h->ts + 4; + + return 0; +} + +static int dvb_net_ule_ts_pusi(struct dvb_net_ule_handle *h) +{ + if (h->ts[1] & TS_PUSI) { + /* Find beginning of first ULE SNDU in current TS cell. */ + /* Synchronize continuity counter. */ + h->priv->tscc = h->ts[3] & 0x0F; + /* There is a pointer field here. */ + if (h->ts[4] > h->ts_remain) { + pr_err("%lu: Invalid ULE packet (pointer field %d)\n", + h->priv->ts_count, h->ts[4]); + h->ts += TS_SZ; + h->priv->ts_count++; + return 1; + } + /* Skip to destination of pointer field. */ + h->from_where = &h->ts[5] + h->ts[4]; + h->ts_remain -= 1 + h->ts[4]; + h->skipped = 0; + } else { + h->skipped++; + h->ts += TS_SZ; + h->priv->ts_count++; + return 1; + } + + return 0; +} + +static int dvb_net_ule_new_ts(struct dvb_net_ule_handle *h) +{ + /* Check continuity counter. */ + if ((h->ts[3] & 0x0F) == h->priv->tscc) + h->priv->tscc = (h->priv->tscc + 1) & 0x0F; + else { + /* TS discontinuity handling: */ + pr_warn("%lu: TS discontinuity: got %#x, expected %#x.\n", + h->priv->ts_count, h->ts[3] & 0x0F, + h->priv->tscc); + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (h->priv->ule_skb) { + dev_kfree_skb(h->priv->ule_skb); + /* Prepare for next SNDU. */ + // reset_ule(h->priv); moved to below. + h->dev->stats.rx_errors++; + h->dev->stats.rx_frame_errors++; + } + reset_ule(h->priv); + /* skip to next PUSI. */ + h->priv->need_pusi = 1; + return 1; + } + /* + * If we still have an incomplete payload, but PUSI is + * set; some TS cells are missing. + * This is only possible here, if we missed exactly 16 TS + * cells (continuity counter wrap). + */ + if (h->ts[1] & TS_PUSI) { + if (!h->priv->need_pusi) { + if (!(*h->from_where < (h->ts_remain-1)) || + *h->from_where != h->priv->ule_sndu_remain) { + /* + * Pointer field is invalid. + * Drop this TS cell and any started ULE SNDU. + */ + pr_warn("%lu: Invalid pointer field: %u.\n", + h->priv->ts_count, + *h->from_where); + + /* + * Drop partly decoded SNDU, reset state, + * resync on PUSI. + */ + if (h->priv->ule_skb) { + h->error = true; + dev_kfree_skb(h->priv->ule_skb); + } + + if (h->error || h->priv->ule_sndu_remain) { + h->dev->stats.rx_errors++; + h->dev->stats.rx_frame_errors++; + h->error = false; + } + + reset_ule(h->priv); + h->priv->need_pusi = 1; + return 1; + } + /* + * Skip pointer field (we're processing a + * packed payload). + */ + h->from_where += 1; + h->ts_remain -= 1; + } else + h->priv->need_pusi = 0; + + if (h->priv->ule_sndu_remain > 183) { + /* + * Current SNDU lacks more data than there + * could be available in the current TS cell. + */ + h->dev->stats.rx_errors++; + h->dev->stats.rx_length_errors++; + pr_warn("%lu: Expected %d more SNDU bytes, but got PUSI (pf %d, h->ts_remain %d). Flushing incomplete payload.\n", + h->priv->ts_count, + h->priv->ule_sndu_remain, + h->ts[4], h->ts_remain); + dev_kfree_skb(h->priv->ule_skb); + /* Prepare for next SNDU. */ + reset_ule(h->priv); + /* + * Resync: go to where pointer field points to: + * start of next ULE SNDU. + */ + h->from_where += h->ts[4]; + h->ts_remain -= h->ts[4]; + } + } + return 0; +} + + +/* + * Start a new payload with skb. + * Find ULE header. It is only guaranteed that the + * length field (2 bytes) is contained in the current + * TS. + * Check h.ts_remain has to be >= 2 here. + */ +static int dvb_net_ule_new_payload(struct dvb_net_ule_handle *h) +{ + if (h->ts_remain < 2) { + pr_warn("Invalid payload packing: only %d bytes left in TS. Resyncing.\n", + h->ts_remain); + h->priv->ule_sndu_len = 0; + h->priv->need_pusi = 1; + h->ts += TS_SZ; + return 1; + } + + if (!h->priv->ule_sndu_len) { + /* Got at least two bytes, thus extrace the SNDU length. */ + h->priv->ule_sndu_len = h->from_where[0] << 8 | + h->from_where[1]; + if (h->priv->ule_sndu_len & 0x8000) { + /* D-Bit is set: no dest mac present. */ + h->priv->ule_sndu_len &= 0x7FFF; + h->priv->ule_dbit = 1; + } else + h->priv->ule_dbit = 0; + + if (h->priv->ule_sndu_len < 5) { + pr_warn("%lu: Invalid ULE SNDU length %u. Resyncing.\n", + h->priv->ts_count, + h->priv->ule_sndu_len); + h->dev->stats.rx_errors++; + h->dev->stats.rx_length_errors++; + h->priv->ule_sndu_len = 0; + h->priv->need_pusi = 1; + h->new_ts = 1; + h->ts += TS_SZ; + h->priv->ts_count++; + return 1; + } + h->ts_remain -= 2; /* consume the 2 bytes SNDU length. */ + h->from_where += 2; + } + + h->priv->ule_sndu_remain = h->priv->ule_sndu_len + 2; + /* + * State of current TS: + * h->ts_remain (remaining bytes in the current TS cell) + * 0 ule_type is not available now, we need the next TS cell + * 1 the first byte of the ule_type is present + * >=2 full ULE header present, maybe some payload data as well. + */ + switch (h->ts_remain) { + case 1: + h->priv->ule_sndu_remain--; + h->priv->ule_sndu_type = h->from_where[0] << 8; + + /* first byte of ule_type is set. */ + h->priv->ule_sndu_type_1 = 1; + h->ts_remain -= 1; + h->from_where += 1; + fallthrough; + case 0: + h->new_ts = 1; + h->ts += TS_SZ; + h->priv->ts_count++; + return 1; + + default: /* complete ULE header is present in current TS. */ + /* Extract ULE type field. */ + if (h->priv->ule_sndu_type_1) { + h->priv->ule_sndu_type_1 = 0; + h->priv->ule_sndu_type |= h->from_where[0]; + h->from_where += 1; /* points to payload start. */ + h->ts_remain -= 1; + } else { + /* Complete type is present in new TS. */ + h->priv->ule_sndu_type = h->from_where[0] << 8 | + h->from_where[1]; + h->from_where += 2; /* points to payload start. */ + h->ts_remain -= 2; + } + break; + } + + /* + * Allocate the skb (decoder target buffer) with the correct size, + * as follows: + * + * prepare for the largest case: bridged SNDU with MAC address + * (dbit = 0). + */ + h->priv->ule_skb = dev_alloc_skb(h->priv->ule_sndu_len + + ETH_HLEN + ETH_ALEN); + if (!h->priv->ule_skb) { + pr_notice("%s: Memory squeeze, dropping packet.\n", + h->dev->name); + h->dev->stats.rx_dropped++; + return -1; + } + + /* This includes the CRC32 _and_ dest mac, if !dbit. */ + h->priv->ule_sndu_remain = h->priv->ule_sndu_len; + h->priv->ule_skb->dev = h->dev; + /* + * Leave space for Ethernet or bridged SNDU header + * (eth hdr plus one MAC addr). + */ + skb_reserve(h->priv->ule_skb, ETH_HLEN + ETH_ALEN); + + return 0; +} + + +static int dvb_net_ule_should_drop(struct dvb_net_ule_handle *h) +{ + static const u8 bc_addr[ETH_ALEN] = { [0 ... ETH_ALEN - 1] = 0xff }; + + /* + * The destination MAC address is the next data in the skb. It comes + * before any extension headers. + * + * Check if the payload of this SNDU should be passed up the stack. + */ + if (h->priv->rx_mode == RX_MODE_PROMISC) + return 0; + + if (h->priv->ule_skb->data[0] & 0x01) { + /* multicast or broadcast */ + if (!ether_addr_equal(h->priv->ule_skb->data, bc_addr)) { + /* multicast */ + if (h->priv->rx_mode == RX_MODE_MULTI) { + int i; + + for (i = 0; i < h->priv->multi_num && + !ether_addr_equal(h->priv->ule_skb->data, + h->priv->multi_macs[i]); + i++) + ; + if (i == h->priv->multi_num) + return 1; + } else if (h->priv->rx_mode != RX_MODE_ALL_MULTI) + return 1; /* no broadcast; */ + /* + * else: + * all multicast mode: accept all multicast packets + */ + } + /* else: broadcast */ + } else if (!ether_addr_equal(h->priv->ule_skb->data, h->dev->dev_addr)) + return 1; + + return 0; +} + + +static void dvb_net_ule_check_crc(struct dvb_net_ule_handle *h, + struct kvec iov[3], + u32 ule_crc, u32 expected_crc) +{ + u8 dest_addr[ETH_ALEN]; + + if (ule_crc != expected_crc) { + pr_warn("%lu: CRC32 check FAILED: %08x / %08x, SNDU len %d type %#x, ts_remain %d, next 2: %x.\n", + h->priv->ts_count, ule_crc, expected_crc, + h->priv->ule_sndu_len, h->priv->ule_sndu_type, + h->ts_remain, + h->ts_remain > 2 ? + *(unsigned short *)h->from_where : 0); + + #ifdef DVB_ULE_DEBUG + hexdump(iov[0].iov_base, iov[0].iov_len); + hexdump(iov[1].iov_base, iov[1].iov_len); + hexdump(iov[2].iov_base, iov[2].iov_len); + + if (ule_where == ule_hist) { + hexdump(&ule_hist[98*TS_SZ], TS_SZ); + hexdump(&ule_hist[99*TS_SZ], TS_SZ); + } else if (ule_where == &ule_hist[TS_SZ]) { + hexdump(&ule_hist[99*TS_SZ], TS_SZ); + hexdump(ule_hist, TS_SZ); + } else { + hexdump(ule_where - TS_SZ - TS_SZ, TS_SZ); + hexdump(ule_where - TS_SZ, TS_SZ); + } + ule_dump = 1; + #endif + + h->dev->stats.rx_errors++; + h->dev->stats.rx_crc_errors++; + dev_kfree_skb(h->priv->ule_skb); + + return; + } + + /* CRC32 verified OK. */ + + /* CRC32 was OK, so remove it from skb. */ + h->priv->ule_skb->tail -= 4; + h->priv->ule_skb->len -= 4; + + if (!h->priv->ule_dbit) { + if (dvb_net_ule_should_drop(h)) { + netdev_dbg(h->dev, + "Dropping SNDU: MAC destination address does not match: dest addr: %pM, h->dev addr: %pM\n", + h->priv->ule_skb->data, h->dev->dev_addr); + dev_kfree_skb(h->priv->ule_skb); + return; + } + + skb_copy_from_linear_data(h->priv->ule_skb, dest_addr, + ETH_ALEN); + skb_pull(h->priv->ule_skb, ETH_ALEN); + } else { + /* dest_addr buffer is only valid if h->priv->ule_dbit == 0 */ + eth_zero_addr(dest_addr); + } + + /* Handle ULE Extension Headers. */ + if (h->priv->ule_sndu_type < ETH_P_802_3_MIN) { + /* There is an extension header. Handle it accordingly. */ + int l = handle_ule_extensions(h->priv); + + if (l < 0) { + /* + * Mandatory extension header unknown or TEST SNDU. + * Drop it. + */ + + // pr_warn("Dropping SNDU, extension headers.\n" ); + dev_kfree_skb(h->priv->ule_skb); + return; + } + skb_pull(h->priv->ule_skb, l); + } + + /* + * Construct/assure correct ethernet header. + * Note: in bridged mode (h->priv->ule_bridged != 0) + * we already have the (original) ethernet + * header at the start of the payload (after + * optional dest. address and any extension + * headers). + */ + if (!h->priv->ule_bridged) { + skb_push(h->priv->ule_skb, ETH_HLEN); + h->ethh = (struct ethhdr *)h->priv->ule_skb->data; + memcpy(h->ethh->h_dest, dest_addr, ETH_ALEN); + eth_zero_addr(h->ethh->h_source); + h->ethh->h_proto = htons(h->priv->ule_sndu_type); + } + /* else: skb is in correct state; nothing to do. */ + h->priv->ule_bridged = 0; + + /* Stuff into kernel's protocol stack. */ + h->priv->ule_skb->protocol = dvb_net_eth_type_trans(h->priv->ule_skb, + h->dev); + /* + * If D-bit is set (i.e. destination MAC address not present), + * receive the packet anyhow. + */ +#if 0 + if (h->priv->ule_dbit && skb->pkt_type == PACKET_OTHERHOST) + h->priv->ule_skb->pkt_type = PACKET_HOST; +#endif + h->dev->stats.rx_packets++; + h->dev->stats.rx_bytes += h->priv->ule_skb->len; + netif_rx(h->priv->ule_skb); +} + +static void dvb_net_ule(struct net_device *dev, const u8 *buf, size_t buf_len) +{ + int ret; + struct dvb_net_ule_handle h = { + .dev = dev, + .priv = netdev_priv(dev), + .ethh = NULL, + .buf = buf, + .buf_len = buf_len, + .skipped = 0L, + .ts = NULL, + .ts_end = NULL, + .from_where = NULL, + .ts_remain = 0, + .how_much = 0, + .new_ts = 1, + .error = false, + }; + + /* + * For all TS cells in current buffer. + * Appearently, we are called for every single TS cell. + */ + for (h.ts = h.buf, h.ts_end = h.buf + h.buf_len; + h.ts < h.ts_end; /* no incr. */) { + if (h.new_ts) { + /* We are about to process a new TS cell. */ + if (dvb_net_ule_new_ts_cell(&h)) + continue; + } + + /* Synchronize on PUSI, if required. */ + if (h.priv->need_pusi) { + if (dvb_net_ule_ts_pusi(&h)) + continue; + } + + if (h.new_ts) { + if (dvb_net_ule_new_ts(&h)) + continue; + } + + /* Check if new payload needs to be started. */ + if (h.priv->ule_skb == NULL) { + ret = dvb_net_ule_new_payload(&h); + if (ret < 0) + return; + if (ret) + continue; + } + + /* Copy data into our current skb. */ + h.how_much = min(h.priv->ule_sndu_remain, (int)h.ts_remain); + skb_put_data(h.priv->ule_skb, h.from_where, h.how_much); + h.priv->ule_sndu_remain -= h.how_much; + h.ts_remain -= h.how_much; + h.from_where += h.how_much; + + /* Check for complete payload. */ + if (h.priv->ule_sndu_remain <= 0) { + /* Check CRC32, we've got it in our skb already. */ + __be16 ulen = htons(h.priv->ule_sndu_len); + __be16 utype = htons(h.priv->ule_sndu_type); + const u8 *tail; + struct kvec iov[3] = { + { &ulen, sizeof ulen }, + { &utype, sizeof utype }, + { h.priv->ule_skb->data, + h.priv->ule_skb->len - 4 } + }; + u32 ule_crc = ~0L, expected_crc; + if (h.priv->ule_dbit) { + /* Set D-bit for CRC32 verification, + * if it was set originally. */ + ulen |= htons(0x8000); + } + + ule_crc = iov_crc32(ule_crc, iov, 3); + tail = skb_tail_pointer(h.priv->ule_skb); + expected_crc = *(tail - 4) << 24 | + *(tail - 3) << 16 | + *(tail - 2) << 8 | + *(tail - 1); + + dvb_net_ule_check_crc(&h, iov, ule_crc, expected_crc); + + /* Prepare for next SNDU. */ + reset_ule(h.priv); + } + + /* More data in current TS (look at the bytes following the CRC32)? */ + if (h.ts_remain >= 2 && *((unsigned short *)h.from_where) != 0xFFFF) { + /* Next ULE SNDU starts right there. */ + h.new_ts = 0; + h.priv->ule_skb = NULL; + h.priv->ule_sndu_type_1 = 0; + h.priv->ule_sndu_len = 0; + // pr_warn("More data in current TS: [%#x %#x %#x %#x]\n", + // *(h.from_where + 0), *(h.from_where + 1), + // *(h.from_where + 2), *(h.from_where + 3)); + // pr_warn("h.ts @ %p, stopped @ %p:\n", h.ts, h.from_where + 0); + // hexdump(h.ts, 188); + } else { + h.new_ts = 1; + h.ts += TS_SZ; + h.priv->ts_count++; + if (h.priv->ule_skb == NULL) { + h.priv->need_pusi = 1; + h.priv->ule_sndu_type_1 = 0; + h.priv->ule_sndu_len = 0; + } + } + } /* for all available TS cells */ +} + +static int dvb_net_ts_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_ts_feed *feed, + u32 *buffer_flags) +{ + struct net_device *dev = feed->priv; + + if (buffer2) + pr_warn("buffer2 not NULL: %p.\n", buffer2); + if (buffer1_len > 32768) + pr_warn("length > 32k: %zu.\n", buffer1_len); + /* pr_info("TS callback: %u bytes, %u TS cells @ %p.\n", + buffer1_len, buffer1_len / TS_SZ, buffer1); */ + dvb_net_ule(dev, buffer1, buffer1_len); + return 0; +} + + +static void dvb_net_sec(struct net_device *dev, + const u8 *pkt, int pkt_len) +{ + u8 *eth; + struct sk_buff *skb; + struct net_device_stats *stats = &dev->stats; + int snap = 0; + + /* note: pkt_len includes a 32bit checksum */ + if (pkt_len < 16) { + pr_warn("%s: IP/MPE packet length = %d too small.\n", + dev->name, pkt_len); + stats->rx_errors++; + stats->rx_length_errors++; + return; + } +/* it seems some ISPs manage to screw up here, so we have to + * relax the error checks... */ +#if 0 + if ((pkt[5] & 0xfd) != 0xc1) { + /* drop scrambled or broken packets */ +#else + if ((pkt[5] & 0x3c) != 0x00) { + /* drop scrambled */ +#endif + stats->rx_errors++; + stats->rx_crc_errors++; + return; + } + if (pkt[5] & 0x02) { + /* handle LLC/SNAP, see rfc-1042 */ + if (pkt_len < 24 || memcmp(&pkt[12], "\xaa\xaa\x03\0\0\0", 6)) { + stats->rx_dropped++; + return; + } + snap = 8; + } + if (pkt[7]) { + /* FIXME: assemble datagram from multiple sections */ + stats->rx_errors++; + stats->rx_frame_errors++; + return; + } + + /* we have 14 byte ethernet header (ip header follows); + * 12 byte MPE header; 4 byte checksum; + 2 byte alignment, 8 byte LLC/SNAP + */ + if (!(skb = dev_alloc_skb(pkt_len - 4 - 12 + 14 + 2 - snap))) { + //pr_notice("%s: Memory squeeze, dropping packet.\n", dev->name); + stats->rx_dropped++; + return; + } + skb_reserve(skb, 2); /* longword align L3 header */ + skb->dev = dev; + + /* copy L3 payload */ + eth = skb_put(skb, pkt_len - 12 - 4 + 14 - snap); + memcpy(eth + 14, pkt + 12 + snap, pkt_len - 12 - 4 - snap); + + /* create ethernet header: */ + eth[0]=pkt[0x0b]; + eth[1]=pkt[0x0a]; + eth[2]=pkt[0x09]; + eth[3]=pkt[0x08]; + eth[4]=pkt[0x04]; + eth[5]=pkt[0x03]; + + eth[6]=eth[7]=eth[8]=eth[9]=eth[10]=eth[11]=0; + + if (snap) { + eth[12] = pkt[18]; + eth[13] = pkt[19]; + } else { + /* protocol numbers are from rfc-1700 or + * http://www.iana.org/assignments/ethernet-numbers + */ + if (pkt[12] >> 4 == 6) { /* version field from IP header */ + eth[12] = 0x86; /* IPv6 */ + eth[13] = 0xdd; + } else { + eth[12] = 0x08; /* IPv4 */ + eth[13] = 0x00; + } + } + + skb->protocol = dvb_net_eth_type_trans(skb, dev); + + stats->rx_packets++; + stats->rx_bytes+=skb->len; + netif_rx(skb); +} + +static int dvb_net_sec_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_section_filter *filter, u32 *buffer_flags) +{ + struct net_device *dev = filter->priv; + + /* + * we rely on the DVB API definition where exactly one complete + * section is delivered in buffer1 + */ + dvb_net_sec (dev, buffer1, buffer1_len); + return 0; +} + +static netdev_tx_t dvb_net_tx(struct sk_buff *skb, struct net_device *dev) +{ + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static u8 mask_normal[6]={0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +static u8 mask_allmulti[6]={0xff, 0xff, 0xff, 0x00, 0x00, 0x00}; +static u8 mac_allmulti[6]={0x01, 0x00, 0x5e, 0x00, 0x00, 0x00}; +static u8 mask_promisc[6]={0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static int dvb_net_filter_sec_set(struct net_device *dev, + struct dmx_section_filter **secfilter, + const u8 *mac, u8 *mac_mask) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + int ret; + + *secfilter=NULL; + ret = priv->secfeed->allocate_filter(priv->secfeed, secfilter); + if (ret<0) { + pr_err("%s: could not get filter\n", dev->name); + return ret; + } + + (*secfilter)->priv=(void *) dev; + + memset((*secfilter)->filter_value, 0x00, DMX_MAX_FILTER_SIZE); + memset((*secfilter)->filter_mask, 0x00, DMX_MAX_FILTER_SIZE); + memset((*secfilter)->filter_mode, 0xff, DMX_MAX_FILTER_SIZE); + + (*secfilter)->filter_value[0]=0x3e; + (*secfilter)->filter_value[3]=mac[5]; + (*secfilter)->filter_value[4]=mac[4]; + (*secfilter)->filter_value[8]=mac[3]; + (*secfilter)->filter_value[9]=mac[2]; + (*secfilter)->filter_value[10]=mac[1]; + (*secfilter)->filter_value[11]=mac[0]; + + (*secfilter)->filter_mask[0] = 0xff; + (*secfilter)->filter_mask[3] = mac_mask[5]; + (*secfilter)->filter_mask[4] = mac_mask[4]; + (*secfilter)->filter_mask[8] = mac_mask[3]; + (*secfilter)->filter_mask[9] = mac_mask[2]; + (*secfilter)->filter_mask[10] = mac_mask[1]; + (*secfilter)->filter_mask[11]=mac_mask[0]; + + netdev_dbg(dev, "filter mac=%pM mask=%pM\n", mac, mac_mask); + + return 0; +} + +static int dvb_net_feed_start(struct net_device *dev) +{ + int ret = 0, i; + struct dvb_net_priv *priv = netdev_priv(dev); + struct dmx_demux *demux = priv->demux; + const unsigned char *mac = (const unsigned char *) dev->dev_addr; + + netdev_dbg(dev, "rx_mode %i\n", priv->rx_mode); + mutex_lock(&priv->mutex); + if (priv->tsfeed || priv->secfeed || priv->secfilter || priv->multi_secfilter[0]) + pr_err("%s: BUG %d\n", __func__, __LINE__); + + priv->secfeed=NULL; + priv->secfilter=NULL; + priv->tsfeed = NULL; + + if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { + netdev_dbg(dev, "alloc secfeed\n"); + ret=demux->allocate_section_feed(demux, &priv->secfeed, + dvb_net_sec_callback); + if (ret<0) { + pr_err("%s: could not allocate section feed\n", + dev->name); + goto error; + } + + ret = priv->secfeed->set(priv->secfeed, priv->pid, 1); + + if (ret<0) { + pr_err("%s: could not set section feed\n", dev->name); + priv->demux->release_section_feed(priv->demux, priv->secfeed); + priv->secfeed=NULL; + goto error; + } + + if (priv->rx_mode != RX_MODE_PROMISC) { + netdev_dbg(dev, "set secfilter\n"); + dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_normal); + } + + switch (priv->rx_mode) { + case RX_MODE_MULTI: + for (i = 0; i < priv->multi_num; i++) { + netdev_dbg(dev, "set multi_secfilter[%d]\n", i); + dvb_net_filter_sec_set(dev, &priv->multi_secfilter[i], + priv->multi_macs[i], mask_normal); + } + break; + case RX_MODE_ALL_MULTI: + priv->multi_num=1; + netdev_dbg(dev, "set multi_secfilter[0]\n"); + dvb_net_filter_sec_set(dev, &priv->multi_secfilter[0], + mac_allmulti, mask_allmulti); + break; + case RX_MODE_PROMISC: + priv->multi_num=0; + netdev_dbg(dev, "set secfilter\n"); + dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_promisc); + break; + } + + netdev_dbg(dev, "start filtering\n"); + priv->secfeed->start_filtering(priv->secfeed); + } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { + ktime_t timeout = ns_to_ktime(10 * NSEC_PER_MSEC); + + /* we have payloads encapsulated in TS */ + netdev_dbg(dev, "alloc tsfeed\n"); + ret = demux->allocate_ts_feed(demux, &priv->tsfeed, dvb_net_ts_callback); + if (ret < 0) { + pr_err("%s: could not allocate ts feed\n", dev->name); + goto error; + } + + /* Set netdevice pointer for ts decaps callback. */ + priv->tsfeed->priv = (void *)dev; + ret = priv->tsfeed->set(priv->tsfeed, + priv->pid, /* pid */ + TS_PACKET, /* type */ + DMX_PES_OTHER, /* pes type */ + timeout /* timeout */ + ); + + if (ret < 0) { + pr_err("%s: could not set ts feed\n", dev->name); + priv->demux->release_ts_feed(priv->demux, priv->tsfeed); + priv->tsfeed = NULL; + goto error; + } + + netdev_dbg(dev, "start filtering\n"); + priv->tsfeed->start_filtering(priv->tsfeed); + } else + ret = -EINVAL; + +error: + mutex_unlock(&priv->mutex); + return ret; +} + +static int dvb_net_feed_stop(struct net_device *dev) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + int i, ret = 0; + + mutex_lock(&priv->mutex); + if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { + if (priv->secfeed) { + if (priv->secfeed->is_filtering) { + netdev_dbg(dev, "stop secfeed\n"); + priv->secfeed->stop_filtering(priv->secfeed); + } + + if (priv->secfilter) { + netdev_dbg(dev, "release secfilter\n"); + priv->secfeed->release_filter(priv->secfeed, + priv->secfilter); + priv->secfilter=NULL; + } + + for (i=0; i<priv->multi_num; i++) { + if (priv->multi_secfilter[i]) { + netdev_dbg(dev, "release multi_filter[%d]\n", + i); + priv->secfeed->release_filter(priv->secfeed, + priv->multi_secfilter[i]); + priv->multi_secfilter[i] = NULL; + } + } + + priv->demux->release_section_feed(priv->demux, priv->secfeed); + priv->secfeed = NULL; + } else + pr_err("%s: no feed to stop\n", dev->name); + } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { + if (priv->tsfeed) { + if (priv->tsfeed->is_filtering) { + netdev_dbg(dev, "stop tsfeed\n"); + priv->tsfeed->stop_filtering(priv->tsfeed); + } + priv->demux->release_ts_feed(priv->demux, priv->tsfeed); + priv->tsfeed = NULL; + } + else + pr_err("%s: no ts feed to stop\n", dev->name); + } else + ret = -EINVAL; + mutex_unlock(&priv->mutex); + return ret; +} + + +static int dvb_set_mc_filter(struct net_device *dev, unsigned char *addr) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + + if (priv->multi_num == DVB_NET_MULTICAST_MAX) + return -ENOMEM; + + memcpy(priv->multi_macs[priv->multi_num], addr, ETH_ALEN); + + priv->multi_num++; + return 0; +} + + +static void wq_set_multicast_list (struct work_struct *work) +{ + struct dvb_net_priv *priv = + container_of(work, struct dvb_net_priv, set_multicast_list_wq); + struct net_device *dev = priv->net; + + dvb_net_feed_stop(dev); + priv->rx_mode = RX_MODE_UNI; + netif_addr_lock_bh(dev); + + if (dev->flags & IFF_PROMISC) { + netdev_dbg(dev, "promiscuous mode\n"); + priv->rx_mode = RX_MODE_PROMISC; + } else if ((dev->flags & IFF_ALLMULTI)) { + netdev_dbg(dev, "allmulti mode\n"); + priv->rx_mode = RX_MODE_ALL_MULTI; + } else if (!netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + + netdev_dbg(dev, "set_mc_list, %d entries\n", + netdev_mc_count(dev)); + + priv->rx_mode = RX_MODE_MULTI; + priv->multi_num = 0; + + netdev_for_each_mc_addr(ha, dev) + dvb_set_mc_filter(dev, ha->addr); + } + + netif_addr_unlock_bh(dev); + dvb_net_feed_start(dev); +} + + +static void dvb_net_set_multicast_list (struct net_device *dev) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + schedule_work(&priv->set_multicast_list_wq); +} + + +static void wq_restart_net_feed (struct work_struct *work) +{ + struct dvb_net_priv *priv = + container_of(work, struct dvb_net_priv, restart_net_feed_wq); + struct net_device *dev = priv->net; + + if (netif_running(dev)) { + dvb_net_feed_stop(dev); + dvb_net_feed_start(dev); + } +} + + +static int dvb_net_set_mac (struct net_device *dev, void *p) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + struct sockaddr *addr=p; + + eth_hw_addr_set(dev, addr->sa_data); + + if (netif_running(dev)) + schedule_work(&priv->restart_net_feed_wq); + + return 0; +} + + +static int dvb_net_open(struct net_device *dev) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + + priv->in_use++; + dvb_net_feed_start(dev); + return 0; +} + + +static int dvb_net_stop(struct net_device *dev) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + + priv->in_use--; + return dvb_net_feed_stop(dev); +} + +static const struct header_ops dvb_header_ops = { + .create = eth_header, + .parse = eth_header_parse, +}; + + +static const struct net_device_ops dvb_netdev_ops = { + .ndo_open = dvb_net_open, + .ndo_stop = dvb_net_stop, + .ndo_start_xmit = dvb_net_tx, + .ndo_set_rx_mode = dvb_net_set_multicast_list, + .ndo_set_mac_address = dvb_net_set_mac, + .ndo_validate_addr = eth_validate_addr, +}; + +static void dvb_net_setup(struct net_device *dev) +{ + ether_setup(dev); + + dev->header_ops = &dvb_header_ops; + dev->netdev_ops = &dvb_netdev_ops; + dev->mtu = 4096; + dev->max_mtu = 4096; + + dev->flags |= IFF_NOARP; +} + +static int get_if(struct dvb_net *dvbnet) +{ + int i; + + for (i=0; i<DVB_NET_DEVICES_MAX; i++) + if (!dvbnet->state[i]) + break; + + if (i == DVB_NET_DEVICES_MAX) + return -1; + + dvbnet->state[i]=1; + return i; +} + +static int dvb_net_add_if(struct dvb_net *dvbnet, u16 pid, u8 feedtype) +{ + struct net_device *net; + struct dvb_net_priv *priv; + int result; + int if_num; + + if (feedtype != DVB_NET_FEEDTYPE_MPE && feedtype != DVB_NET_FEEDTYPE_ULE) + return -EINVAL; + if ((if_num = get_if(dvbnet)) < 0) + return -EINVAL; + + net = alloc_netdev(sizeof(struct dvb_net_priv), "dvb", + NET_NAME_UNKNOWN, dvb_net_setup); + if (!net) + return -ENOMEM; + + if (dvbnet->dvbdev->id) + snprintf(net->name, IFNAMSIZ, "dvb%d%u%d", + dvbnet->dvbdev->adapter->num, dvbnet->dvbdev->id, if_num); + else + /* compatibility fix to keep dvb0_0 format */ + snprintf(net->name, IFNAMSIZ, "dvb%d_%d", + dvbnet->dvbdev->adapter->num, if_num); + + net->addr_len = 6; + eth_hw_addr_set(net, dvbnet->dvbdev->adapter->proposed_mac); + + dvbnet->device[if_num] = net; + + priv = netdev_priv(net); + priv->net = net; + priv->demux = dvbnet->demux; + priv->pid = pid; + priv->rx_mode = RX_MODE_UNI; + priv->need_pusi = 1; + priv->tscc = 0; + priv->feedtype = feedtype; + reset_ule(priv); + + INIT_WORK(&priv->set_multicast_list_wq, wq_set_multicast_list); + INIT_WORK(&priv->restart_net_feed_wq, wq_restart_net_feed); + mutex_init(&priv->mutex); + + net->base_addr = pid; + + if ((result = register_netdev(net)) < 0) { + dvbnet->device[if_num] = NULL; + free_netdev(net); + return result; + } + pr_info("created network interface %s\n", net->name); + + return if_num; +} + +static int dvb_net_remove_if(struct dvb_net *dvbnet, unsigned long num) +{ + struct net_device *net = dvbnet->device[num]; + struct dvb_net_priv *priv; + + if (!dvbnet->state[num]) + return -EINVAL; + priv = netdev_priv(net); + if (priv->in_use) + return -EBUSY; + + dvb_net_stop(net); + flush_work(&priv->set_multicast_list_wq); + flush_work(&priv->restart_net_feed_wq); + pr_info("removed network interface %s\n", net->name); + unregister_netdev(net); + dvbnet->state[num]=0; + dvbnet->device[num] = NULL; + free_netdev(net); + + return 0; +} + +static int dvb_net_do_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_net *dvbnet = dvbdev->priv; + int ret = 0; + + if (((file->f_flags&O_ACCMODE)==O_RDONLY)) + return -EPERM; + + if (mutex_lock_interruptible(&dvbnet->ioctl_mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case NET_ADD_IF: + { + struct dvb_net_if *dvbnetif = parg; + int result; + + if (!capable(CAP_SYS_ADMIN)) { + ret = -EPERM; + goto ioctl_error; + } + + if (!try_module_get(dvbdev->adapter->module)) { + ret = -EPERM; + goto ioctl_error; + } + + result=dvb_net_add_if(dvbnet, dvbnetif->pid, dvbnetif->feedtype); + if (result<0) { + module_put(dvbdev->adapter->module); + ret = result; + goto ioctl_error; + } + dvbnetif->if_num=result; + break; + } + case NET_GET_IF: + { + struct net_device *netdev; + struct dvb_net_priv *priv_data; + struct dvb_net_if *dvbnetif = parg; + int if_num = dvbnetif->if_num; + + if (if_num >= DVB_NET_DEVICES_MAX) { + ret = -EINVAL; + goto ioctl_error; + } + if_num = array_index_nospec(if_num, DVB_NET_DEVICES_MAX); + + if (!dvbnet->state[if_num]) { + ret = -EINVAL; + goto ioctl_error; + } + + netdev = dvbnet->device[if_num]; + + priv_data = netdev_priv(netdev); + dvbnetif->pid=priv_data->pid; + dvbnetif->feedtype=priv_data->feedtype; + break; + } + case NET_REMOVE_IF: + { + if (!capable(CAP_SYS_ADMIN)) { + ret = -EPERM; + goto ioctl_error; + } + if ((unsigned long) parg >= DVB_NET_DEVICES_MAX) { + ret = -EINVAL; + goto ioctl_error; + } + ret = dvb_net_remove_if(dvbnet, (unsigned long) parg); + if (!ret) + module_put(dvbdev->adapter->module); + break; + } + + /* binary compatibility cruft */ + case __NET_ADD_IF_OLD: + { + struct __dvb_net_if_old *dvbnetif = parg; + int result; + + if (!capable(CAP_SYS_ADMIN)) { + ret = -EPERM; + goto ioctl_error; + } + + if (!try_module_get(dvbdev->adapter->module)) { + ret = -EPERM; + goto ioctl_error; + } + + result=dvb_net_add_if(dvbnet, dvbnetif->pid, DVB_NET_FEEDTYPE_MPE); + if (result<0) { + module_put(dvbdev->adapter->module); + ret = result; + goto ioctl_error; + } + dvbnetif->if_num=result; + break; + } + case __NET_GET_IF_OLD: + { + struct net_device *netdev; + struct dvb_net_priv *priv_data; + struct __dvb_net_if_old *dvbnetif = parg; + int if_num = dvbnetif->if_num; + + if (if_num >= DVB_NET_DEVICES_MAX) { + ret = -EINVAL; + goto ioctl_error; + } + if_num = array_index_nospec(if_num, DVB_NET_DEVICES_MAX); + + if (!dvbnet->state[if_num]) { + ret = -EINVAL; + goto ioctl_error; + } + + netdev = dvbnet->device[if_num]; + + priv_data = netdev_priv(netdev); + dvbnetif->pid=priv_data->pid; + break; + } + default: + ret = -ENOTTY; + break; + } + +ioctl_error: + mutex_unlock(&dvbnet->ioctl_mutex); + return ret; +} + +static long dvb_net_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(file, cmd, arg, dvb_net_do_ioctl); +} + +static int locked_dvb_net_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_net *dvbnet = dvbdev->priv; + int ret; + + if (mutex_lock_interruptible(&dvbnet->remove_mutex)) + return -ERESTARTSYS; + + if (dvbnet->exit) { + mutex_unlock(&dvbnet->remove_mutex); + return -ENODEV; + } + + ret = dvb_generic_open(inode, file); + + mutex_unlock(&dvbnet->remove_mutex); + + return ret; +} + +static int dvb_net_close(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_net *dvbnet = dvbdev->priv; + + mutex_lock(&dvbnet->remove_mutex); + + dvb_generic_release(inode, file); + + if (dvbdev->users == 1 && dvbnet->exit == 1) { + mutex_unlock(&dvbnet->remove_mutex); + wake_up(&dvbdev->wait_queue); + } else { + mutex_unlock(&dvbnet->remove_mutex); + } + + return 0; +} + + +static const struct file_operations dvb_net_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dvb_net_ioctl, + .open = locked_dvb_net_open, + .release = dvb_net_close, + .llseek = noop_llseek, +}; + +static const struct dvb_device dvbdev_net = { + .priv = NULL, + .users = 1, + .writers = 1, +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + .name = "dvb-net", +#endif + .fops = &dvb_net_fops, +}; + +void dvb_net_release (struct dvb_net *dvbnet) +{ + int i; + + mutex_lock(&dvbnet->remove_mutex); + dvbnet->exit = 1; + mutex_unlock(&dvbnet->remove_mutex); + + if (dvbnet->dvbdev->users < 1) + wait_event(dvbnet->dvbdev->wait_queue, + dvbnet->dvbdev->users == 1); + + dvb_unregister_device(dvbnet->dvbdev); + + for (i=0; i<DVB_NET_DEVICES_MAX; i++) { + if (!dvbnet->state[i]) + continue; + dvb_net_remove_if(dvbnet, i); + } +} +EXPORT_SYMBOL(dvb_net_release); + + +int dvb_net_init (struct dvb_adapter *adap, struct dvb_net *dvbnet, + struct dmx_demux *dmx) +{ + int i; + + mutex_init(&dvbnet->ioctl_mutex); + mutex_init(&dvbnet->remove_mutex); + dvbnet->demux = dmx; + + for (i=0; i<DVB_NET_DEVICES_MAX; i++) + dvbnet->state[i] = 0; + + return dvb_register_device(adap, &dvbnet->dvbdev, &dvbdev_net, + dvbnet, DVB_DEVICE_NET, 0); +} +EXPORT_SYMBOL(dvb_net_init); diff --git a/drivers/media/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb-core/dvb_ringbuffer.c new file mode 100644 index 000000000..d1d471af0 --- /dev/null +++ b/drivers/media/dvb-core/dvb_ringbuffer.c @@ -0,0 +1,369 @@ +/* + * + * dvb_ringbuffer.c: ring buffer implementation for the dvb driver + * + * Copyright (C) 2003 Oliver Endriss + * Copyright (C) 2004 Andrew de Quincey + * + * based on code originally found in av7110.c & dvb_ci.c: + * Copyright (C) 1999-2003 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 Lesser General Public License for more details. + */ + + + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/uaccess.h> + +#include <media/dvb_ringbuffer.h> + +#define PKT_READY 0 +#define PKT_DISPOSED 1 + + +void dvb_ringbuffer_init(struct dvb_ringbuffer *rbuf, void *data, size_t len) +{ + rbuf->pread=rbuf->pwrite=0; + rbuf->data=data; + rbuf->size=len; + rbuf->error=0; + + init_waitqueue_head(&rbuf->queue); + + spin_lock_init(&(rbuf->lock)); +} + + + +int dvb_ringbuffer_empty(struct dvb_ringbuffer *rbuf) +{ + /* smp_load_acquire() to load write pointer on reader side + * this pairs with smp_store_release() in dvb_ringbuffer_write(), + * dvb_ringbuffer_write_user(), or dvb_ringbuffer_reset() + * + * for memory barriers also see Documentation/core-api/circular-buffers.rst + */ + return (rbuf->pread == smp_load_acquire(&rbuf->pwrite)); +} + + + +ssize_t dvb_ringbuffer_free(struct dvb_ringbuffer *rbuf) +{ + ssize_t free; + + /* READ_ONCE() to load read pointer on writer side + * this pairs with smp_store_release() in dvb_ringbuffer_read(), + * dvb_ringbuffer_read_user(), dvb_ringbuffer_flush(), + * or dvb_ringbuffer_reset() + */ + free = READ_ONCE(rbuf->pread) - rbuf->pwrite; + if (free <= 0) + free += rbuf->size; + return free-1; +} + + + +ssize_t dvb_ringbuffer_avail(struct dvb_ringbuffer *rbuf) +{ + ssize_t avail; + + /* smp_load_acquire() to load write pointer on reader side + * this pairs with smp_store_release() in dvb_ringbuffer_write(), + * dvb_ringbuffer_write_user(), or dvb_ringbuffer_reset() + */ + avail = smp_load_acquire(&rbuf->pwrite) - rbuf->pread; + if (avail < 0) + avail += rbuf->size; + return avail; +} + + + +void dvb_ringbuffer_flush(struct dvb_ringbuffer *rbuf) +{ + /* dvb_ringbuffer_flush() counts as read operation + * smp_load_acquire() to load write pointer + * smp_store_release() to update read pointer, this ensures that the + * correct pointer is visible for subsequent dvb_ringbuffer_free() + * calls on other cpu cores + */ + smp_store_release(&rbuf->pread, smp_load_acquire(&rbuf->pwrite)); + rbuf->error = 0; +} +EXPORT_SYMBOL(dvb_ringbuffer_flush); + +void dvb_ringbuffer_reset(struct dvb_ringbuffer *rbuf) +{ + /* dvb_ringbuffer_reset() counts as read and write operation + * smp_store_release() to update read pointer + */ + smp_store_release(&rbuf->pread, 0); + /* smp_store_release() to update write pointer */ + smp_store_release(&rbuf->pwrite, 0); + rbuf->error = 0; +} + +void dvb_ringbuffer_flush_spinlock_wakeup(struct dvb_ringbuffer *rbuf) +{ + unsigned long flags; + + spin_lock_irqsave(&rbuf->lock, flags); + dvb_ringbuffer_flush(rbuf); + spin_unlock_irqrestore(&rbuf->lock, flags); + + wake_up(&rbuf->queue); +} + +ssize_t dvb_ringbuffer_read_user(struct dvb_ringbuffer *rbuf, u8 __user *buf, size_t len) +{ + size_t todo = len; + size_t split; + + split = (rbuf->pread + len > rbuf->size) ? rbuf->size - rbuf->pread : 0; + if (split > 0) { + if (copy_to_user(buf, rbuf->data+rbuf->pread, split)) + return -EFAULT; + buf += split; + todo -= split; + /* smp_store_release() for read pointer update to ensure + * that buf is not overwritten until read is complete, + * this pairs with READ_ONCE() in dvb_ringbuffer_free() + */ + smp_store_release(&rbuf->pread, 0); + } + if (copy_to_user(buf, rbuf->data+rbuf->pread, todo)) + return -EFAULT; + + /* smp_store_release() to update read pointer, see above */ + smp_store_release(&rbuf->pread, (rbuf->pread + todo) % rbuf->size); + + return len; +} + +void dvb_ringbuffer_read(struct dvb_ringbuffer *rbuf, u8 *buf, size_t len) +{ + size_t todo = len; + size_t split; + + split = (rbuf->pread + len > rbuf->size) ? rbuf->size - rbuf->pread : 0; + if (split > 0) { + memcpy(buf, rbuf->data+rbuf->pread, split); + buf += split; + todo -= split; + /* smp_store_release() for read pointer update to ensure + * that buf is not overwritten until read is complete, + * this pairs with READ_ONCE() in dvb_ringbuffer_free() + */ + smp_store_release(&rbuf->pread, 0); + } + memcpy(buf, rbuf->data+rbuf->pread, todo); + + /* smp_store_release() to update read pointer, see above */ + smp_store_release(&rbuf->pread, (rbuf->pread + todo) % rbuf->size); +} + + +ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, size_t len) +{ + size_t todo = len; + size_t split; + + split = (rbuf->pwrite + len > rbuf->size) ? rbuf->size - rbuf->pwrite : 0; + + if (split > 0) { + memcpy(rbuf->data+rbuf->pwrite, buf, split); + buf += split; + todo -= split; + /* smp_store_release() for write pointer update to ensure that + * written data is visible on other cpu cores before the pointer + * update, this pairs with smp_load_acquire() in + * dvb_ringbuffer_empty() or dvb_ringbuffer_avail() + */ + smp_store_release(&rbuf->pwrite, 0); + } + memcpy(rbuf->data+rbuf->pwrite, buf, todo); + /* smp_store_release() for write pointer update, see above */ + smp_store_release(&rbuf->pwrite, (rbuf->pwrite + todo) % rbuf->size); + + return len; +} + +ssize_t dvb_ringbuffer_write_user(struct dvb_ringbuffer *rbuf, + const u8 __user *buf, size_t len) +{ + int status; + size_t todo = len; + size_t split; + + split = (rbuf->pwrite + len > rbuf->size) ? rbuf->size - rbuf->pwrite : 0; + + if (split > 0) { + status = copy_from_user(rbuf->data+rbuf->pwrite, buf, split); + if (status) + return len - todo; + buf += split; + todo -= split; + /* smp_store_release() for write pointer update to ensure that + * written data is visible on other cpu cores before the pointer + * update, this pairs with smp_load_acquire() in + * dvb_ringbuffer_empty() or dvb_ringbuffer_avail() + */ + smp_store_release(&rbuf->pwrite, 0); + } + status = copy_from_user(rbuf->data+rbuf->pwrite, buf, todo); + if (status) + return len - todo; + /* smp_store_release() for write pointer update, see above */ + smp_store_release(&rbuf->pwrite, (rbuf->pwrite + todo) % rbuf->size); + + return len; +} + +ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8* buf, size_t len) +{ + int status; + ssize_t oldpwrite = rbuf->pwrite; + + DVB_RINGBUFFER_WRITE_BYTE(rbuf, len >> 8); + DVB_RINGBUFFER_WRITE_BYTE(rbuf, len & 0xff); + DVB_RINGBUFFER_WRITE_BYTE(rbuf, PKT_READY); + status = dvb_ringbuffer_write(rbuf, buf, len); + + if (status < 0) rbuf->pwrite = oldpwrite; + return status; +} + +ssize_t dvb_ringbuffer_pkt_read_user(struct dvb_ringbuffer *rbuf, size_t idx, + int offset, u8 __user *buf, size_t len) +{ + size_t todo; + size_t split; + size_t pktlen; + + pktlen = rbuf->data[idx] << 8; + pktlen |= rbuf->data[(idx + 1) % rbuf->size]; + if (offset > pktlen) return -EINVAL; + if ((offset + len) > pktlen) len = pktlen - offset; + + idx = (idx + DVB_RINGBUFFER_PKTHDRSIZE + offset) % rbuf->size; + todo = len; + split = ((idx + len) > rbuf->size) ? rbuf->size - idx : 0; + if (split > 0) { + if (copy_to_user(buf, rbuf->data+idx, split)) + return -EFAULT; + buf += split; + todo -= split; + idx = 0; + } + if (copy_to_user(buf, rbuf->data+idx, todo)) + return -EFAULT; + + return len; +} + +ssize_t dvb_ringbuffer_pkt_read(struct dvb_ringbuffer *rbuf, size_t idx, + int offset, u8* buf, size_t len) +{ + size_t todo; + size_t split; + size_t pktlen; + + pktlen = rbuf->data[idx] << 8; + pktlen |= rbuf->data[(idx + 1) % rbuf->size]; + if (offset > pktlen) return -EINVAL; + if ((offset + len) > pktlen) len = pktlen - offset; + + idx = (idx + DVB_RINGBUFFER_PKTHDRSIZE + offset) % rbuf->size; + todo = len; + split = ((idx + len) > rbuf->size) ? rbuf->size - idx : 0; + if (split > 0) { + memcpy(buf, rbuf->data+idx, split); + buf += split; + todo -= split; + idx = 0; + } + memcpy(buf, rbuf->data+idx, todo); + return len; +} + +void dvb_ringbuffer_pkt_dispose(struct dvb_ringbuffer *rbuf, size_t idx) +{ + size_t pktlen; + + rbuf->data[(idx + 2) % rbuf->size] = PKT_DISPOSED; + + // clean up disposed packets + while(dvb_ringbuffer_avail(rbuf) > DVB_RINGBUFFER_PKTHDRSIZE) { + if (DVB_RINGBUFFER_PEEK(rbuf, 2) == PKT_DISPOSED) { + pktlen = DVB_RINGBUFFER_PEEK(rbuf, 0) << 8; + pktlen |= DVB_RINGBUFFER_PEEK(rbuf, 1); + DVB_RINGBUFFER_SKIP(rbuf, pktlen + DVB_RINGBUFFER_PKTHDRSIZE); + } else { + // first packet is not disposed, so we stop cleaning now + break; + } + } +} + +ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* pktlen) +{ + int consumed; + int curpktlen; + int curpktstatus; + + if (idx == -1) { + idx = rbuf->pread; + } else { + curpktlen = rbuf->data[idx] << 8; + curpktlen |= rbuf->data[(idx + 1) % rbuf->size]; + idx = (idx + curpktlen + DVB_RINGBUFFER_PKTHDRSIZE) % rbuf->size; + } + + consumed = (idx - rbuf->pread) % rbuf->size; + + while((dvb_ringbuffer_avail(rbuf) - consumed) > DVB_RINGBUFFER_PKTHDRSIZE) { + + curpktlen = rbuf->data[idx] << 8; + curpktlen |= rbuf->data[(idx + 1) % rbuf->size]; + curpktstatus = rbuf->data[(idx + 2) % rbuf->size]; + + if (curpktstatus == PKT_READY) { + *pktlen = curpktlen; + return idx; + } + + consumed += curpktlen + DVB_RINGBUFFER_PKTHDRSIZE; + idx = (idx + curpktlen + DVB_RINGBUFFER_PKTHDRSIZE) % rbuf->size; + } + + // no packets available + return -1; +} + + + +EXPORT_SYMBOL(dvb_ringbuffer_init); +EXPORT_SYMBOL(dvb_ringbuffer_empty); +EXPORT_SYMBOL(dvb_ringbuffer_free); +EXPORT_SYMBOL(dvb_ringbuffer_avail); +EXPORT_SYMBOL(dvb_ringbuffer_flush_spinlock_wakeup); +EXPORT_SYMBOL(dvb_ringbuffer_read_user); +EXPORT_SYMBOL(dvb_ringbuffer_read); +EXPORT_SYMBOL(dvb_ringbuffer_write); +EXPORT_SYMBOL(dvb_ringbuffer_write_user); diff --git a/drivers/media/dvb-core/dvb_vb2.c b/drivers/media/dvb-core/dvb_vb2.c new file mode 100644 index 000000000..909df82fe --- /dev/null +++ b/drivers/media/dvb-core/dvb_vb2.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dvb-vb2.c - dvb-vb2 + * + * Copyright (C) 2015 Samsung Electronics + * + * Author: jh1009.sung@samsung.com + */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mm.h> + +#include <media/dvbdev.h> +#include <media/dvb_vb2.h> + +#define DVB_V2_MAX_SIZE (4096 * 188) + +static int vb2_debug; +module_param(vb2_debug, int, 0644); + +#define dprintk(level, fmt, arg...) \ + do { \ + if (vb2_debug >= level) \ + pr_info("vb2: %s: " fmt, __func__, ## arg); \ + } while (0) + +static int _queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq); + + ctx->buf_cnt = *nbuffers; + *nplanes = 1; + sizes[0] = ctx->buf_siz; + + /* + * videobuf2-vmalloc allocator is context-less so no need to set + * alloc_ctxs array. + */ + + dprintk(3, "[%s] count=%d, size=%d\n", ctx->name, + *nbuffers, sizes[0]); + + return 0; +} + +static int _buffer_prepare(struct vb2_buffer *vb) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = ctx->buf_siz; + + if (vb2_plane_size(vb, 0) < size) { + dprintk(1, "[%s] data will not fit into plane (%lu < %lu)\n", + ctx->name, vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + dprintk(3, "[%s]\n", ctx->name); + + return 0; +} + +static void _buffer_queue(struct vb2_buffer *vb) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct dvb_buffer *buf = container_of(vb, struct dvb_buffer, vb); + unsigned long flags = 0; + + spin_lock_irqsave(&ctx->slock, flags); + list_add_tail(&buf->list, &ctx->dvb_q); + spin_unlock_irqrestore(&ctx->slock, flags); + + dprintk(3, "[%s]\n", ctx->name); +} + +static int _start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq); + + dprintk(3, "[%s] count=%d\n", ctx->name, count); + return 0; +} + +static void _stop_streaming(struct vb2_queue *vq) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq); + struct dvb_buffer *buf; + unsigned long flags = 0; + + dprintk(3, "[%s]\n", ctx->name); + + spin_lock_irqsave(&ctx->slock, flags); + while (!list_empty(&ctx->dvb_q)) { + buf = list_entry(ctx->dvb_q.next, + struct dvb_buffer, list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + list_del(&buf->list); + } + spin_unlock_irqrestore(&ctx->slock, flags); +} + +static void _dmxdev_lock(struct vb2_queue *vq) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq); + + mutex_lock(&ctx->mutex); + dprintk(3, "[%s]\n", ctx->name); +} + +static void _dmxdev_unlock(struct vb2_queue *vq) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq); + + if (mutex_is_locked(&ctx->mutex)) + mutex_unlock(&ctx->mutex); + dprintk(3, "[%s]\n", ctx->name); +} + +static const struct vb2_ops dvb_vb2_qops = { + .queue_setup = _queue_setup, + .buf_prepare = _buffer_prepare, + .buf_queue = _buffer_queue, + .start_streaming = _start_streaming, + .stop_streaming = _stop_streaming, + .wait_prepare = _dmxdev_unlock, + .wait_finish = _dmxdev_lock, +}; + +static void _fill_dmx_buffer(struct vb2_buffer *vb, void *pb) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct dmx_buffer *b = pb; + + b->index = vb->index; + b->length = vb->planes[0].length; + b->bytesused = vb->planes[0].bytesused; + b->offset = vb->planes[0].m.offset; + dprintk(3, "[%s]\n", ctx->name); +} + +static int _fill_vb2_buffer(struct vb2_buffer *vb, struct vb2_plane *planes) +{ + struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + planes[0].bytesused = 0; + dprintk(3, "[%s]\n", ctx->name); + + return 0; +} + +static const struct vb2_buf_ops dvb_vb2_buf_ops = { + .fill_user_buffer = _fill_dmx_buffer, + .fill_vb2_buffer = _fill_vb2_buffer, +}; + +/* + * Videobuf operations + */ +int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, int nonblocking) +{ + struct vb2_queue *q = &ctx->vb_q; + int ret; + + memset(ctx, 0, sizeof(struct dvb_vb2_ctx)); + q->type = DVB_BUF_TYPE_CAPTURE; + /**capture type*/ + q->is_output = 0; + /**only mmap is supported currently*/ + q->io_modes = VB2_MMAP; + q->drv_priv = ctx; + q->buf_struct_size = sizeof(struct dvb_buffer); + q->min_buffers_needed = 1; + q->ops = &dvb_vb2_qops; + q->mem_ops = &vb2_vmalloc_memops; + q->buf_ops = &dvb_vb2_buf_ops; + q->num_buffers = 0; + ret = vb2_core_queue_init(q); + if (ret) { + ctx->state = DVB_VB2_STATE_NONE; + dprintk(1, "[%s] errno=%d\n", ctx->name, ret); + return ret; + } + + mutex_init(&ctx->mutex); + spin_lock_init(&ctx->slock); + INIT_LIST_HEAD(&ctx->dvb_q); + + strscpy(ctx->name, name, DVB_VB2_NAME_MAX); + ctx->nonblocking = nonblocking; + ctx->state = DVB_VB2_STATE_INIT; + + dprintk(3, "[%s]\n", ctx->name); + + return 0; +} + +int dvb_vb2_release(struct dvb_vb2_ctx *ctx) +{ + struct vb2_queue *q = (struct vb2_queue *)&ctx->vb_q; + + if (ctx->state & DVB_VB2_STATE_INIT) + vb2_core_queue_release(q); + + ctx->state = DVB_VB2_STATE_NONE; + dprintk(3, "[%s]\n", ctx->name); + + return 0; +} + +int dvb_vb2_stream_on(struct dvb_vb2_ctx *ctx) +{ + struct vb2_queue *q = &ctx->vb_q; + int ret; + + ret = vb2_core_streamon(q, q->type); + if (ret) { + ctx->state = DVB_VB2_STATE_NONE; + dprintk(1, "[%s] errno=%d\n", ctx->name, ret); + return ret; + } + ctx->state |= DVB_VB2_STATE_STREAMON; + dprintk(3, "[%s]\n", ctx->name); + + return 0; +} + +int dvb_vb2_stream_off(struct dvb_vb2_ctx *ctx) +{ + struct vb2_queue *q = (struct vb2_queue *)&ctx->vb_q; + int ret; + + ctx->state &= ~DVB_VB2_STATE_STREAMON; + ret = vb2_core_streamoff(q, q->type); + if (ret) { + ctx->state = DVB_VB2_STATE_NONE; + dprintk(1, "[%s] errno=%d\n", ctx->name, ret); + return ret; + } + dprintk(3, "[%s]\n", ctx->name); + + return 0; +} + +int dvb_vb2_is_streaming(struct dvb_vb2_ctx *ctx) +{ + return (ctx->state & DVB_VB2_STATE_STREAMON); +} + +int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx, + const unsigned char *src, int len, + enum dmx_buffer_flags *buffer_flags) +{ + unsigned long flags = 0; + void *vbuf = NULL; + int todo = len; + unsigned char *psrc = (unsigned char *)src; + int ll = 0; + + /* + * normal case: This func is called twice from demux driver + * one with valid src pointer, second time with NULL pointer + */ + if (!src || !len) + return 0; + spin_lock_irqsave(&ctx->slock, flags); + if (buffer_flags && *buffer_flags) { + ctx->flags |= *buffer_flags; + *buffer_flags = 0; + } + while (todo) { + if (!ctx->buf) { + if (list_empty(&ctx->dvb_q)) { + dprintk(3, "[%s] Buffer overflow!!!\n", + ctx->name); + break; + } + + ctx->buf = list_entry(ctx->dvb_q.next, + struct dvb_buffer, list); + ctx->remain = vb2_plane_size(&ctx->buf->vb, 0); + ctx->offset = 0; + } + + if (!dvb_vb2_is_streaming(ctx)) { + vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_ERROR); + list_del(&ctx->buf->list); + ctx->buf = NULL; + break; + } + + /* Fill buffer */ + ll = min(todo, ctx->remain); + vbuf = vb2_plane_vaddr(&ctx->buf->vb, 0); + memcpy(vbuf + ctx->offset, psrc, ll); + todo -= ll; + psrc += ll; + + ctx->remain -= ll; + ctx->offset += ll; + + if (ctx->remain == 0) { + vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE); + list_del(&ctx->buf->list); + ctx->buf = NULL; + } + } + + if (ctx->nonblocking && ctx->buf) { + vb2_set_plane_payload(&ctx->buf->vb, 0, ll); + vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE); + list_del(&ctx->buf->list); + ctx->buf = NULL; + } + spin_unlock_irqrestore(&ctx->slock, flags); + + if (todo) + dprintk(1, "[%s] %d bytes are dropped.\n", ctx->name, todo); + else + dprintk(3, "[%s]\n", ctx->name); + + dprintk(3, "[%s] %d bytes are copied\n", ctx->name, len - todo); + return (len - todo); +} + +int dvb_vb2_reqbufs(struct dvb_vb2_ctx *ctx, struct dmx_requestbuffers *req) +{ + int ret; + + /* Adjust size to a sane value */ + if (req->size > DVB_V2_MAX_SIZE) + req->size = DVB_V2_MAX_SIZE; + + /* FIXME: round req->size to a 188 or 204 multiple */ + + ctx->buf_siz = req->size; + ctx->buf_cnt = req->count; + ret = vb2_core_reqbufs(&ctx->vb_q, VB2_MEMORY_MMAP, 0, &req->count); + if (ret) { + ctx->state = DVB_VB2_STATE_NONE; + dprintk(1, "[%s] count=%d size=%d errno=%d\n", ctx->name, + ctx->buf_cnt, ctx->buf_siz, ret); + return ret; + } + ctx->state |= DVB_VB2_STATE_REQBUFS; + dprintk(3, "[%s] count=%d size=%d\n", ctx->name, + ctx->buf_cnt, ctx->buf_siz); + + return 0; +} + +int dvb_vb2_querybuf(struct dvb_vb2_ctx *ctx, struct dmx_buffer *b) +{ + struct vb2_queue *q = &ctx->vb_q; + + if (b->index >= q->num_buffers) { + dprintk(1, "[%s] buffer index out of range\n", ctx->name); + return -EINVAL; + } + vb2_core_querybuf(&ctx->vb_q, b->index, b); + dprintk(3, "[%s] index=%d\n", ctx->name, b->index); + return 0; +} + +int dvb_vb2_expbuf(struct dvb_vb2_ctx *ctx, struct dmx_exportbuffer *exp) +{ + struct vb2_queue *q = &ctx->vb_q; + int ret; + + ret = vb2_core_expbuf(&ctx->vb_q, &exp->fd, q->type, exp->index, + 0, exp->flags); + if (ret) { + dprintk(1, "[%s] index=%d errno=%d\n", ctx->name, + exp->index, ret); + return ret; + } + dprintk(3, "[%s] index=%d fd=%d\n", ctx->name, exp->index, exp->fd); + + return 0; +} + +int dvb_vb2_qbuf(struct dvb_vb2_ctx *ctx, struct dmx_buffer *b) +{ + struct vb2_queue *q = &ctx->vb_q; + int ret; + + if (b->index >= q->num_buffers) { + dprintk(1, "[%s] buffer index out of range\n", ctx->name); + return -EINVAL; + } + ret = vb2_core_qbuf(&ctx->vb_q, b->index, b, NULL); + if (ret) { + dprintk(1, "[%s] index=%d errno=%d\n", ctx->name, + b->index, ret); + return ret; + } + dprintk(5, "[%s] index=%d\n", ctx->name, b->index); + + return 0; +} + +int dvb_vb2_dqbuf(struct dvb_vb2_ctx *ctx, struct dmx_buffer *b) +{ + unsigned long flags; + int ret; + + ret = vb2_core_dqbuf(&ctx->vb_q, &b->index, b, ctx->nonblocking); + if (ret) { + dprintk(1, "[%s] errno=%d\n", ctx->name, ret); + return ret; + } + + spin_lock_irqsave(&ctx->slock, flags); + b->count = ctx->count++; + b->flags = ctx->flags; + ctx->flags = 0; + spin_unlock_irqrestore(&ctx->slock, flags); + + dprintk(5, "[%s] index=%d, count=%d, flags=%d\n", + ctx->name, b->index, ctx->count, b->flags); + + + return 0; +} + +int dvb_vb2_mmap(struct dvb_vb2_ctx *ctx, struct vm_area_struct *vma) +{ + int ret; + + ret = vb2_mmap(&ctx->vb_q, vma); + if (ret) { + dprintk(1, "[%s] errno=%d\n", ctx->name, ret); + return ret; + } + dprintk(3, "[%s] ret=%d\n", ctx->name, ret); + + return 0; +} + +__poll_t dvb_vb2_poll(struct dvb_vb2_ctx *ctx, struct file *file, + poll_table *wait) +{ + dprintk(3, "[%s]\n", ctx->name); + return vb2_core_poll(&ctx->vb_q, file, wait); +} + diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbdev.c new file mode 100644 index 000000000..d352e0284 --- /dev/null +++ b/drivers/media/dvb-core/dvbdev.c @@ -0,0 +1,1137 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * dvbdev.c + * + * Copyright (C) 2000 Ralph Metzler <ralph@convergence.de> + * & Marcus Metzler <marcus@convergence.de> + * for convergence integrated media GmbH + */ + +#define pr_fmt(fmt) "dvbdev: " fmt + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/mutex.h> +#include <media/dvbdev.h> + +/* Due to enum tuner_pad_index */ +#include <media/tuner.h> + +static DEFINE_MUTEX(dvbdev_mutex); +static LIST_HEAD(dvbdevfops_list); +static int dvbdev_debug; + +module_param(dvbdev_debug, int, 0644); +MODULE_PARM_DESC(dvbdev_debug, "Turn on/off device debugging (default:off)."); + +#define dprintk(fmt, arg...) do { \ + if (dvbdev_debug) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##arg); \ +} while (0) + +static LIST_HEAD(dvb_adapter_list); +static DEFINE_MUTEX(dvbdev_register_lock); + +static const char * const dnames[] = { + [DVB_DEVICE_VIDEO] = "video", + [DVB_DEVICE_AUDIO] = "audio", + [DVB_DEVICE_SEC] = "sec", + [DVB_DEVICE_FRONTEND] = "frontend", + [DVB_DEVICE_DEMUX] = "demux", + [DVB_DEVICE_DVR] = "dvr", + [DVB_DEVICE_CA] = "ca", + [DVB_DEVICE_NET] = "net", + [DVB_DEVICE_OSD] = "osd" +}; + +#ifdef CONFIG_DVB_DYNAMIC_MINORS +#define MAX_DVB_MINORS 256 +#define DVB_MAX_IDS MAX_DVB_MINORS +#else +#define DVB_MAX_IDS 4 + +static const u8 minor_type[] = { + [DVB_DEVICE_VIDEO] = 0, + [DVB_DEVICE_AUDIO] = 1, + [DVB_DEVICE_SEC] = 2, + [DVB_DEVICE_FRONTEND] = 3, + [DVB_DEVICE_DEMUX] = 4, + [DVB_DEVICE_DVR] = 5, + [DVB_DEVICE_CA] = 6, + [DVB_DEVICE_NET] = 7, + [DVB_DEVICE_OSD] = 8, +}; + +#define nums2minor(num, type, id) \ + (((num) << 6) | ((id) << 4) | minor_type[type]) + +#define MAX_DVB_MINORS (DVB_MAX_ADAPTERS*64) +#endif + +static struct class *dvb_class; + +static struct dvb_device *dvb_minors[MAX_DVB_MINORS]; +static DECLARE_RWSEM(minor_rwsem); + +static int dvb_device_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev; + + mutex_lock(&dvbdev_mutex); + down_read(&minor_rwsem); + dvbdev = dvb_minors[iminor(inode)]; + + if (dvbdev && dvbdev->fops) { + int err = 0; + const struct file_operations *new_fops; + + new_fops = fops_get(dvbdev->fops); + if (!new_fops) + goto fail; + file->private_data = dvb_device_get(dvbdev); + replace_fops(file, new_fops); + if (file->f_op->open) + err = file->f_op->open(inode, file); + up_read(&minor_rwsem); + mutex_unlock(&dvbdev_mutex); + if (err) + dvb_device_put(dvbdev); + return err; + } +fail: + up_read(&minor_rwsem); + mutex_unlock(&dvbdev_mutex); + return -ENODEV; +} + + +static const struct file_operations dvb_device_fops = +{ + .owner = THIS_MODULE, + .open = dvb_device_open, + .llseek = noop_llseek, +}; + +static struct cdev dvb_device_cdev; + +int dvb_generic_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->users) + return -EBUSY; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if (!dvbdev->readers) + return -EBUSY; + dvbdev->readers--; + } else { + if (!dvbdev->writers) + return -EBUSY; + dvbdev->writers--; + } + + dvbdev->users--; + return 0; +} +EXPORT_SYMBOL(dvb_generic_open); + + +int dvb_generic_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + dvbdev->readers++; + } else { + dvbdev->writers++; + } + + dvbdev->users++; + + dvb_device_put(dvbdev); + + return 0; +} +EXPORT_SYMBOL(dvb_generic_release); + + +long dvb_generic_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->kernel_ioctl) + return -EINVAL; + + return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl); +} +EXPORT_SYMBOL(dvb_generic_ioctl); + + +static int dvbdev_get_free_id (struct dvb_adapter *adap, int type) +{ + u32 id = 0; + + while (id < DVB_MAX_IDS) { + struct dvb_device *dev; + list_for_each_entry(dev, &adap->device_list, list_head) + if (dev->type == type && dev->id == id) + goto skip; + return id; +skip: + id++; + } + return -ENFILE; +} + +static void dvb_media_device_free(struct dvb_device *dvbdev) +{ +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + if (dvbdev->entity) { + media_device_unregister_entity(dvbdev->entity); + kfree(dvbdev->entity); + kfree(dvbdev->pads); + dvbdev->entity = NULL; + dvbdev->pads = NULL; + } + + if (dvbdev->tsout_entity) { + int i; + + for (i = 0; i < dvbdev->tsout_num_entities; i++) { + media_device_unregister_entity(&dvbdev->tsout_entity[i]); + kfree(dvbdev->tsout_entity[i].name); + } + kfree(dvbdev->tsout_entity); + kfree(dvbdev->tsout_pads); + dvbdev->tsout_entity = NULL; + dvbdev->tsout_pads = NULL; + + dvbdev->tsout_num_entities = 0; + } + + if (dvbdev->intf_devnode) { + media_devnode_remove(dvbdev->intf_devnode); + dvbdev->intf_devnode = NULL; + } + + if (dvbdev->adapter->conn) { + media_device_unregister_entity(dvbdev->adapter->conn); + kfree(dvbdev->adapter->conn); + dvbdev->adapter->conn = NULL; + kfree(dvbdev->adapter->conn_pads); + dvbdev->adapter->conn_pads = NULL; + } +#endif +} + +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) +static int dvb_create_tsout_entity(struct dvb_device *dvbdev, + const char *name, int npads) +{ + int i, ret = 0; + + dvbdev->tsout_pads = kcalloc(npads, sizeof(*dvbdev->tsout_pads), + GFP_KERNEL); + if (!dvbdev->tsout_pads) + return -ENOMEM; + + dvbdev->tsout_entity = kcalloc(npads, sizeof(*dvbdev->tsout_entity), + GFP_KERNEL); + if (!dvbdev->tsout_entity) + return -ENOMEM; + + dvbdev->tsout_num_entities = npads; + + for (i = 0; i < npads; i++) { + struct media_pad *pads = &dvbdev->tsout_pads[i]; + struct media_entity *entity = &dvbdev->tsout_entity[i]; + + entity->name = kasprintf(GFP_KERNEL, "%s #%d", name, i); + if (!entity->name) + return -ENOMEM; + + entity->function = MEDIA_ENT_F_IO_DTV; + pads->flags = MEDIA_PAD_FL_SINK; + + ret = media_entity_pads_init(entity, 1, pads); + if (ret < 0) + return ret; + + ret = media_device_register_entity(dvbdev->adapter->mdev, + entity); + if (ret < 0) + return ret; + } + return 0; +} + +#define DEMUX_TSOUT "demux-tsout" +#define DVR_TSOUT "dvr-tsout" + +static int dvb_create_media_entity(struct dvb_device *dvbdev, + int type, int demux_sink_pads) +{ + int i, ret, npads; + + switch (type) { + case DVB_DEVICE_FRONTEND: + npads = 2; + break; + case DVB_DEVICE_DVR: + ret = dvb_create_tsout_entity(dvbdev, DVR_TSOUT, + demux_sink_pads); + return ret; + case DVB_DEVICE_DEMUX: + npads = 1 + demux_sink_pads; + ret = dvb_create_tsout_entity(dvbdev, DEMUX_TSOUT, + demux_sink_pads); + if (ret < 0) + return ret; + break; + case DVB_DEVICE_CA: + npads = 2; + break; + case DVB_DEVICE_NET: + /* + * We should be creating entities for the MPE/ULE + * decapsulation hardware (or software implementation). + * + * However, the number of for the MPE/ULE decaps may not be + * fixed. As we don't have yet dynamic support for PADs at + * the Media Controller, let's not create the decap + * entities yet. + */ + return 0; + default: + return 0; + } + + dvbdev->entity = kzalloc(sizeof(*dvbdev->entity), GFP_KERNEL); + if (!dvbdev->entity) + return -ENOMEM; + + dvbdev->entity->name = dvbdev->name; + + if (npads) { + dvbdev->pads = kcalloc(npads, sizeof(*dvbdev->pads), + GFP_KERNEL); + if (!dvbdev->pads) { + kfree(dvbdev->entity); + dvbdev->entity = NULL; + return -ENOMEM; + } + } + + switch (type) { + case DVB_DEVICE_FRONTEND: + dvbdev->entity->function = MEDIA_ENT_F_DTV_DEMOD; + dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK; + dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE; + break; + case DVB_DEVICE_DEMUX: + dvbdev->entity->function = MEDIA_ENT_F_TS_DEMUX; + dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK; + for (i = 1; i < npads; i++) + dvbdev->pads[i].flags = MEDIA_PAD_FL_SOURCE; + break; + case DVB_DEVICE_CA: + dvbdev->entity->function = MEDIA_ENT_F_DTV_CA; + dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK; + dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE; + break; + default: + /* Should never happen, as the first switch prevents it */ + kfree(dvbdev->entity); + kfree(dvbdev->pads); + dvbdev->entity = NULL; + dvbdev->pads = NULL; + return 0; + } + + if (npads) { + ret = media_entity_pads_init(dvbdev->entity, npads, dvbdev->pads); + if (ret) + return ret; + } + ret = media_device_register_entity(dvbdev->adapter->mdev, + dvbdev->entity); + if (ret) + return ret; + + pr_info("%s: media entity '%s' registered.\n", + __func__, dvbdev->entity->name); + + return 0; +} +#endif + +static int dvb_register_media_device(struct dvb_device *dvbdev, + int type, int minor, + unsigned demux_sink_pads) +{ +#if defined(CONFIG_MEDIA_CONTROLLER_DVB) + struct media_link *link; + u32 intf_type; + int ret; + + if (!dvbdev->adapter->mdev) + return 0; + + ret = dvb_create_media_entity(dvbdev, type, demux_sink_pads); + if (ret) + return ret; + + switch (type) { + case DVB_DEVICE_FRONTEND: + intf_type = MEDIA_INTF_T_DVB_FE; + break; + case DVB_DEVICE_DEMUX: + intf_type = MEDIA_INTF_T_DVB_DEMUX; + break; + case DVB_DEVICE_DVR: + intf_type = MEDIA_INTF_T_DVB_DVR; + break; + case DVB_DEVICE_CA: + intf_type = MEDIA_INTF_T_DVB_CA; + break; + case DVB_DEVICE_NET: + intf_type = MEDIA_INTF_T_DVB_NET; + break; + default: + return 0; + } + + dvbdev->intf_devnode = media_devnode_create(dvbdev->adapter->mdev, + intf_type, 0, + DVB_MAJOR, minor); + + if (!dvbdev->intf_devnode) + return -ENOMEM; + + /* + * Create the "obvious" link, e. g. the ones that represent + * a direct association between an interface and an entity. + * Other links should be created elsewhere, like: + * DVB FE intf -> tuner + * DVB demux intf -> dvr + */ + + if (!dvbdev->entity) + return 0; + + link = media_create_intf_link(dvbdev->entity, + &dvbdev->intf_devnode->intf, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (!link) + return -ENOMEM; +#endif + return 0; +} + +int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, + const struct dvb_device *template, void *priv, + enum dvb_device_type type, int demux_sink_pads) +{ + struct dvb_device *dvbdev; + struct file_operations *dvbdevfops = NULL; + struct dvbdevfops_node *node = NULL, *new_node = NULL; + struct device *clsdev; + int minor; + int id, ret; + + mutex_lock(&dvbdev_register_lock); + + if ((id = dvbdev_get_free_id (adap, type)) < 0) { + mutex_unlock(&dvbdev_register_lock); + *pdvbdev = NULL; + pr_err("%s: couldn't find free device id\n", __func__); + return -ENFILE; + } + + *pdvbdev = dvbdev = kzalloc(sizeof(*dvbdev), GFP_KERNEL); + if (!dvbdev){ + mutex_unlock(&dvbdev_register_lock); + return -ENOMEM; + } + + /* + * When a device of the same type is probe()d more than once, + * the first allocated fops are used. This prevents memory leaks + * that can occur when the same device is probe()d repeatedly. + */ + list_for_each_entry(node, &dvbdevfops_list, list_head) { + if (node->fops->owner == adap->module && + node->type == type && + node->template == template) { + dvbdevfops = node->fops; + break; + } + } + + if (dvbdevfops == NULL) { + dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL); + if (!dvbdevfops) { + kfree(dvbdev); + mutex_unlock(&dvbdev_register_lock); + return -ENOMEM; + } + + new_node = kzalloc(sizeof(struct dvbdevfops_node), GFP_KERNEL); + if (!new_node) { + kfree(dvbdevfops); + kfree(dvbdev); + mutex_unlock(&dvbdev_register_lock); + return -ENOMEM; + } + + new_node->fops = dvbdevfops; + new_node->type = type; + new_node->template = template; + list_add_tail (&new_node->list_head, &dvbdevfops_list); + } + + memcpy(dvbdev, template, sizeof(struct dvb_device)); + kref_init(&dvbdev->ref); + dvbdev->type = type; + dvbdev->id = id; + dvbdev->adapter = adap; + dvbdev->priv = priv; + dvbdev->fops = dvbdevfops; + init_waitqueue_head (&dvbdev->wait_queue); + dvbdevfops->owner = adap->module; + list_add_tail (&dvbdev->list_head, &adap->device_list); + down_write(&minor_rwsem); +#ifdef CONFIG_DVB_DYNAMIC_MINORS + for (minor = 0; minor < MAX_DVB_MINORS; minor++) + if (dvb_minors[minor] == NULL) + break; + if (minor == MAX_DVB_MINORS) { + if (new_node) { + list_del (&new_node->list_head); + kfree(dvbdevfops); + kfree(new_node); + } + list_del (&dvbdev->list_head); + kfree(dvbdev); + up_write(&minor_rwsem); + mutex_unlock(&dvbdev_register_lock); + return -EINVAL; + } +#else + minor = nums2minor(adap->num, type, id); +#endif + dvbdev->minor = minor; + dvb_minors[minor] = dvb_device_get(dvbdev); + up_write(&minor_rwsem); + ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads); + if (ret) { + pr_err("%s: dvb_register_media_device failed to create the mediagraph\n", + __func__); + if (new_node) { + list_del (&new_node->list_head); + kfree(dvbdevfops); + kfree(new_node); + } + dvb_media_device_free(dvbdev); + list_del (&dvbdev->list_head); + kfree(dvbdev); + mutex_unlock(&dvbdev_register_lock); + return ret; + } + + clsdev = device_create(dvb_class, adap->device, + MKDEV(DVB_MAJOR, minor), + dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id); + if (IS_ERR(clsdev)) { + pr_err("%s: failed to create device dvb%d.%s%d (%ld)\n", + __func__, adap->num, dnames[type], id, PTR_ERR(clsdev)); + if (new_node) { + list_del (&new_node->list_head); + kfree(dvbdevfops); + kfree(new_node); + } + dvb_media_device_free(dvbdev); + list_del (&dvbdev->list_head); + kfree(dvbdev); + mutex_unlock(&dvbdev_register_lock); + return PTR_ERR(clsdev); + } + + dprintk("DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n", + adap->num, dnames[type], id, minor, minor); + + mutex_unlock(&dvbdev_register_lock); + return 0; +} +EXPORT_SYMBOL(dvb_register_device); + + +void dvb_remove_device(struct dvb_device *dvbdev) +{ + if (!dvbdev) + return; + + down_write(&minor_rwsem); + dvb_minors[dvbdev->minor] = NULL; + dvb_device_put(dvbdev); + up_write(&minor_rwsem); + + dvb_media_device_free(dvbdev); + + device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor)); + + list_del (&dvbdev->list_head); +} +EXPORT_SYMBOL(dvb_remove_device); + + +static void dvb_free_device(struct kref *ref) +{ + struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref); + + kfree (dvbdev); +} + + +struct dvb_device *dvb_device_get(struct dvb_device *dvbdev) +{ + kref_get(&dvbdev->ref); + return dvbdev; +} +EXPORT_SYMBOL(dvb_device_get); + + +void dvb_device_put(struct dvb_device *dvbdev) +{ + if (dvbdev) + kref_put(&dvbdev->ref, dvb_free_device); +} + + +void dvb_unregister_device(struct dvb_device *dvbdev) +{ + dvb_remove_device(dvbdev); + dvb_device_put(dvbdev); +} +EXPORT_SYMBOL(dvb_unregister_device); + + +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + +static int dvb_create_io_intf_links(struct dvb_adapter *adap, + struct media_interface *intf, + char *name) +{ + struct media_device *mdev = adap->mdev; + struct media_entity *entity; + struct media_link *link; + + media_device_for_each_entity(entity, mdev) { + if (entity->function == MEDIA_ENT_F_IO_DTV) { + if (strncmp(entity->name, name, strlen(name))) + continue; + link = media_create_intf_link(entity, intf, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (!link) + return -ENOMEM; + } + } + return 0; +} + +int dvb_create_media_graph(struct dvb_adapter *adap, + bool create_rf_connector) +{ + struct media_device *mdev = adap->mdev; + struct media_entity *entity, *tuner = NULL, *demod = NULL, *conn; + struct media_entity *demux = NULL, *ca = NULL; + struct media_link *link; + struct media_interface *intf; + unsigned demux_pad = 0; + unsigned dvr_pad = 0; + unsigned ntuner = 0, ndemod = 0; + int ret, pad_source, pad_sink; + static const char *connector_name = "Television"; + + if (!mdev) + return 0; + + media_device_for_each_entity(entity, mdev) { + switch (entity->function) { + case MEDIA_ENT_F_TUNER: + tuner = entity; + ntuner++; + break; + case MEDIA_ENT_F_DTV_DEMOD: + demod = entity; + ndemod++; + break; + case MEDIA_ENT_F_TS_DEMUX: + demux = entity; + break; + case MEDIA_ENT_F_DTV_CA: + ca = entity; + break; + } + } + + /* + * Prepare to signalize to media_create_pad_links() that multiple + * entities of the same type exists and a 1:n or n:1 links need to be + * created. + * NOTE: if both tuner and demod have multiple instances, it is up + * to the caller driver to create such links. + */ + if (ntuner > 1) + tuner = NULL; + if (ndemod > 1) + demod = NULL; + + if (create_rf_connector) { + conn = kzalloc(sizeof(*conn), GFP_KERNEL); + if (!conn) + return -ENOMEM; + adap->conn = conn; + + adap->conn_pads = kzalloc(sizeof(*adap->conn_pads), GFP_KERNEL); + if (!adap->conn_pads) + return -ENOMEM; + + conn->flags = MEDIA_ENT_FL_CONNECTOR; + conn->function = MEDIA_ENT_F_CONN_RF; + conn->name = connector_name; + adap->conn_pads->flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(conn, 1, adap->conn_pads); + if (ret) + return ret; + + ret = media_device_register_entity(mdev, conn); + if (ret) + return ret; + + if (!ntuner) { + ret = media_create_pad_links(mdev, + MEDIA_ENT_F_CONN_RF, + conn, 0, + MEDIA_ENT_F_DTV_DEMOD, + demod, 0, + MEDIA_LNK_FL_ENABLED, + false); + } else { + pad_sink = media_get_pad_index(tuner, true, + PAD_SIGNAL_ANALOG); + if (pad_sink < 0) + return -EINVAL; + ret = media_create_pad_links(mdev, + MEDIA_ENT_F_CONN_RF, + conn, 0, + MEDIA_ENT_F_TUNER, + tuner, pad_sink, + MEDIA_LNK_FL_ENABLED, + false); + } + if (ret) + return ret; + } + + if (ntuner && ndemod) { + /* NOTE: first found tuner source pad presumed correct */ + pad_source = media_get_pad_index(tuner, false, + PAD_SIGNAL_ANALOG); + if (pad_source < 0) + return -EINVAL; + ret = media_create_pad_links(mdev, + MEDIA_ENT_F_TUNER, + tuner, pad_source, + MEDIA_ENT_F_DTV_DEMOD, + demod, 0, MEDIA_LNK_FL_ENABLED, + false); + if (ret) + return ret; + } + + if (ndemod && demux) { + ret = media_create_pad_links(mdev, + MEDIA_ENT_F_DTV_DEMOD, + demod, 1, + MEDIA_ENT_F_TS_DEMUX, + demux, 0, MEDIA_LNK_FL_ENABLED, + false); + if (ret) + return ret; + } + if (demux && ca) { + ret = media_create_pad_link(demux, 1, ca, + 0, MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + } + + /* Create demux links for each ringbuffer/pad */ + if (demux) { + media_device_for_each_entity(entity, mdev) { + if (entity->function == MEDIA_ENT_F_IO_DTV) { + if (!strncmp(entity->name, DVR_TSOUT, + strlen(DVR_TSOUT))) { + ret = media_create_pad_link(demux, + ++dvr_pad, + entity, 0, 0); + if (ret) + return ret; + } + if (!strncmp(entity->name, DEMUX_TSOUT, + strlen(DEMUX_TSOUT))) { + ret = media_create_pad_link(demux, + ++demux_pad, + entity, 0, 0); + if (ret) + return ret; + } + } + } + } + + /* Create interface links for FE->tuner, DVR->demux and CA->ca */ + media_device_for_each_intf(intf, mdev) { + if (intf->type == MEDIA_INTF_T_DVB_CA && ca) { + link = media_create_intf_link(ca, intf, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (!link) + return -ENOMEM; + } + + if (intf->type == MEDIA_INTF_T_DVB_FE && tuner) { + link = media_create_intf_link(tuner, intf, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (!link) + return -ENOMEM; + } +#if 0 + /* + * Indirect link - let's not create yet, as we don't know how + * to handle indirect links, nor if this will + * actually be needed. + */ + if (intf->type == MEDIA_INTF_T_DVB_DVR && demux) { + link = media_create_intf_link(demux, intf, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (!link) + return -ENOMEM; + } +#endif + if (intf->type == MEDIA_INTF_T_DVB_DVR) { + ret = dvb_create_io_intf_links(adap, intf, DVR_TSOUT); + if (ret) + return ret; + } + if (intf->type == MEDIA_INTF_T_DVB_DEMUX) { + ret = dvb_create_io_intf_links(adap, intf, DEMUX_TSOUT); + if (ret) + return ret; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(dvb_create_media_graph); +#endif + +static int dvbdev_check_free_adapter_num(int num) +{ + struct list_head *entry; + list_for_each(entry, &dvb_adapter_list) { + struct dvb_adapter *adap; + adap = list_entry(entry, struct dvb_adapter, list_head); + if (adap->num == num) + return 0; + } + return 1; +} + +static int dvbdev_get_free_adapter_num (void) +{ + int num = 0; + + while (num < DVB_MAX_ADAPTERS) { + if (dvbdev_check_free_adapter_num(num)) + return num; + num++; + } + + return -ENFILE; +} + + +int dvb_register_adapter(struct dvb_adapter *adap, const char *name, + struct module *module, struct device *device, + short *adapter_nums) +{ + int i, num; + + mutex_lock(&dvbdev_register_lock); + + for (i = 0; i < DVB_MAX_ADAPTERS; ++i) { + num = adapter_nums[i]; + if (num >= 0 && num < DVB_MAX_ADAPTERS) { + /* use the one the driver asked for */ + if (dvbdev_check_free_adapter_num(num)) + break; + } else { + num = dvbdev_get_free_adapter_num(); + break; + } + num = -1; + } + + if (num < 0) { + mutex_unlock(&dvbdev_register_lock); + return -ENFILE; + } + + memset (adap, 0, sizeof(struct dvb_adapter)); + INIT_LIST_HEAD (&adap->device_list); + + pr_info("DVB: registering new adapter (%s)\n", name); + + adap->num = num; + adap->name = name; + adap->module = module; + adap->device = device; + adap->mfe_shared = 0; + adap->mfe_dvbdev = NULL; + mutex_init (&adap->mfe_lock); + +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + mutex_init(&adap->mdev_lock); +#endif + + list_add_tail (&adap->list_head, &dvb_adapter_list); + + mutex_unlock(&dvbdev_register_lock); + + return num; +} +EXPORT_SYMBOL(dvb_register_adapter); + + +int dvb_unregister_adapter(struct dvb_adapter *adap) +{ + mutex_lock(&dvbdev_register_lock); + list_del (&adap->list_head); + mutex_unlock(&dvbdev_register_lock); + return 0; +} +EXPORT_SYMBOL(dvb_unregister_adapter); + +/* if the miracle happens and "generic_usercopy()" is included into + the kernel, then this can vanish. please don't make the mistake and + define this as video_usercopy(). this will introduce a dependency + to the v4l "videodev.o" module, which is unnecessary for some + cards (ie. the budget dvb-cards don't need the v4l module...) */ +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct file *file, + unsigned int cmd, void *arg)) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -EINVAL; + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + /* + * For this command, the pointer is actually an integer + * argument. + */ + parg = (void *) arg; + break; + case _IOC_READ: /* some v4l ioctls are marked wrong ... */ + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD) + err = -ENOTTY; + + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + kfree(mbuf); + return err; +} + +#if IS_ENABLED(CONFIG_I2C) +struct i2c_client *dvb_module_probe(const char *module_name, + const char *name, + struct i2c_adapter *adap, + unsigned char addr, + void *platform_data) +{ + struct i2c_client *client; + struct i2c_board_info *board_info; + + board_info = kzalloc(sizeof(*board_info), GFP_KERNEL); + if (!board_info) + return NULL; + + if (name) + strscpy(board_info->type, name, I2C_NAME_SIZE); + else + strscpy(board_info->type, module_name, I2C_NAME_SIZE); + + board_info->addr = addr; + board_info->platform_data = platform_data; + request_module(module_name); + client = i2c_new_client_device(adap, board_info); + if (!i2c_client_has_driver(client)) { + kfree(board_info); + return NULL; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + client = NULL; + } + + kfree(board_info); + return client; +} +EXPORT_SYMBOL_GPL(dvb_module_probe); + +void dvb_module_release(struct i2c_client *client) +{ + if (!client) + return; + + module_put(client->dev.driver->owner); + i2c_unregister_device(client); +} +EXPORT_SYMBOL_GPL(dvb_module_release); +#endif + +static int dvb_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct dvb_device *dvbdev = dev_get_drvdata(dev); + + add_uevent_var(env, "DVB_ADAPTER_NUM=%d", dvbdev->adapter->num); + add_uevent_var(env, "DVB_DEVICE_TYPE=%s", dnames[dvbdev->type]); + add_uevent_var(env, "DVB_DEVICE_NUM=%d", dvbdev->id); + return 0; +} + +static char *dvb_devnode(struct device *dev, umode_t *mode) +{ + struct dvb_device *dvbdev = dev_get_drvdata(dev); + + return kasprintf(GFP_KERNEL, "dvb/adapter%d/%s%d", + dvbdev->adapter->num, dnames[dvbdev->type], dvbdev->id); +} + + +static int __init init_dvbdev(void) +{ + int retval; + dev_t dev = MKDEV(DVB_MAJOR, 0); + + if ((retval = register_chrdev_region(dev, MAX_DVB_MINORS, "DVB")) != 0) { + pr_err("dvb-core: unable to get major %d\n", DVB_MAJOR); + return retval; + } + + cdev_init(&dvb_device_cdev, &dvb_device_fops); + if ((retval = cdev_add(&dvb_device_cdev, dev, MAX_DVB_MINORS)) != 0) { + pr_err("dvb-core: unable register character device\n"); + goto error; + } + + dvb_class = class_create(THIS_MODULE, "dvb"); + if (IS_ERR(dvb_class)) { + retval = PTR_ERR(dvb_class); + goto error; + } + dvb_class->dev_uevent = dvb_uevent; + dvb_class->devnode = dvb_devnode; + return 0; + +error: + cdev_del(&dvb_device_cdev); + unregister_chrdev_region(dev, MAX_DVB_MINORS); + return retval; +} + + +static void __exit exit_dvbdev(void) +{ + struct dvbdevfops_node *node, *next; + + class_destroy(dvb_class); + cdev_del(&dvb_device_cdev); + unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS); + + list_for_each_entry_safe(node, next, &dvbdevfops_list, list_head) { + list_del (&node->list_head); + kfree(node->fops); + kfree(node); + } +} + +subsys_initcall(init_dvbdev); +module_exit(exit_dvbdev); + +MODULE_DESCRIPTION("DVB Core Driver"); +MODULE_AUTHOR("Marcus Metzler, Ralph Metzler, Holger Waechtler"); +MODULE_LICENSE("GPL"); |