diff options
author | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-06-14 16:35:53 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-08-13 23:08:14 -0300 |
commit | 3d6c2bc08ac4f75bf3597740357c98f2207ca412 (patch) | |
tree | dfe6d7c9e466cef06224ffadd3310d61f41a4678 /drivers/media/dvb-core | |
parent | 5bc3cb743bbab408792c1b4ef31adf6268aa4b7e (diff) | |
download | blackbird-op-linux-3d6c2bc08ac4f75bf3597740357c98f2207ca412.tar.gz blackbird-op-linux-3d6c2bc08ac4f75bf3597740357c98f2207ca412.zip |
[media] dvb: move the dvb core one level up
just like the V4L2 core, move the DVB core to drivers/media, as the
intention is to get rid of both "video" and "dvb" directories.
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/dvb-core')
22 files changed, 12182 insertions, 0 deletions
diff --git a/drivers/media/dvb-core/Kconfig b/drivers/media/dvb-core/Kconfig new file mode 100644 index 000000000000..fa7a2490ed5f --- /dev/null +++ b/drivers/media/dvb-core/Kconfig @@ -0,0 +1,29 @@ +# +# DVB device configuration +# + +config DVB_MAX_ADAPTERS + int "maximum number of DVB/ATSC adapters" + depends on DVB_CORE + default 8 + 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 8 + +config DVB_DYNAMIC_MINORS + bool "Dynamic DVB minor allocation" + depends on DVB_CORE + default n + 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. diff --git a/drivers/media/dvb-core/Makefile b/drivers/media/dvb-core/Makefile new file mode 100644 index 000000000000..8f22bcd7c1f9 --- /dev/null +++ b/drivers/media/dvb-core/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the kernel DVB device drivers. +# + +dvb-net-$(CONFIG_DVB_NET) := dvb_net.o + +dvb-core-objs := dvbdev.o dmxdev.o dvb_demux.o dvb_filter.o \ + dvb_ca_en50221.o dvb_frontend.o \ + $(dvb-net-y) dvb_ringbuffer.o dvb_math.o + +obj-$(CONFIG_DVB_CORE) += dvb-core.o diff --git a/drivers/media/dvb-core/demux.h b/drivers/media/dvb-core/demux.h new file mode 100644 index 000000000000..eb91fd808c16 --- /dev/null +++ b/drivers/media/dvb-core/demux.h @@ -0,0 +1,280 @@ +/* + * demux.h + * + * Copyright (c) 2002 Convergence GmbH + * + * based on code: + * Copyright (c) 2000 Nokia Research Center + * Tampere, FINLAND + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __DEMUX_H +#define __DEMUX_H + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/time.h> +#include <linux/dvb/dmx.h> + +/*--------------------------------------------------------------------------*/ +/* Common definitions */ +/*--------------------------------------------------------------------------*/ + +/* + * DMX_MAX_FILTER_SIZE: Maximum length (in bytes) of a section/PES filter. + */ + +#ifndef DMX_MAX_FILTER_SIZE +#define DMX_MAX_FILTER_SIZE 18 +#endif + +/* + * DMX_MAX_SECFEED_SIZE: Maximum length (in bytes) of a private section feed filter. + */ + +#ifndef DMX_MAX_SECTION_SIZE +#define DMX_MAX_SECTION_SIZE 4096 +#endif +#ifndef DMX_MAX_SECFEED_SIZE +#define DMX_MAX_SECFEED_SIZE (DMX_MAX_SECTION_SIZE + 188) +#endif + + +/* + * enum dmx_success: Success codes for the Demux Callback API. + */ + +enum dmx_success { + DMX_OK = 0, /* Received Ok */ + DMX_LENGTH_ERROR, /* Incorrect length */ + DMX_OVERRUN_ERROR, /* Receiver ring buffer overrun */ + DMX_CRC_ERROR, /* Incorrect CRC */ + DMX_FRAME_ERROR, /* Frame alignment error */ + DMX_FIFO_ERROR, /* Receiver FIFO overrun */ + DMX_MISSED_ERROR /* Receiver missed packet */ +} ; + +/*--------------------------------------------------------------------------*/ +/* TS packet reception */ +/*--------------------------------------------------------------------------*/ + +/* TS filter type for set() */ + +#define TS_PACKET 1 /* send TS packets (188 bytes) to callback (default) */ +#define TS_PAYLOAD_ONLY 2 /* in case TS_PACKET is set, only send the TS + payload (<=184 bytes per packet) to callback */ +#define TS_DECODER 4 /* send stream to built-in decoder (if present) */ +#define TS_DEMUX 8 /* in case TS_PACKET is set, send the TS to + the demux device, not to the dvr device */ + +/* PES type for filters which write to built-in decoder */ +/* these should be kept identical to the types in dmx.h */ + +enum dmx_ts_pes +{ /* also send packets to decoder (if it exists) */ + DMX_TS_PES_AUDIO0, + DMX_TS_PES_VIDEO0, + DMX_TS_PES_TELETEXT0, + DMX_TS_PES_SUBTITLE0, + DMX_TS_PES_PCR0, + + DMX_TS_PES_AUDIO1, + DMX_TS_PES_VIDEO1, + DMX_TS_PES_TELETEXT1, + DMX_TS_PES_SUBTITLE1, + DMX_TS_PES_PCR1, + + DMX_TS_PES_AUDIO2, + DMX_TS_PES_VIDEO2, + DMX_TS_PES_TELETEXT2, + DMX_TS_PES_SUBTITLE2, + DMX_TS_PES_PCR2, + + DMX_TS_PES_AUDIO3, + DMX_TS_PES_VIDEO3, + DMX_TS_PES_TELETEXT3, + DMX_TS_PES_SUBTITLE3, + DMX_TS_PES_PCR3, + + DMX_TS_PES_OTHER +}; + +#define DMX_TS_PES_AUDIO DMX_TS_PES_AUDIO0 +#define DMX_TS_PES_VIDEO DMX_TS_PES_VIDEO0 +#define DMX_TS_PES_TELETEXT DMX_TS_PES_TELETEXT0 +#define DMX_TS_PES_SUBTITLE DMX_TS_PES_SUBTITLE0 +#define DMX_TS_PES_PCR DMX_TS_PES_PCR0 + + +struct dmx_ts_feed { + int is_filtering; /* Set to non-zero when filtering in progress */ + struct dmx_demux *parent; /* Back-pointer */ + void *priv; /* Pointer to private data of the API client */ + int (*set) (struct dmx_ts_feed *feed, + u16 pid, + int type, + enum dmx_ts_pes pes_type, + size_t circular_buffer_size, + struct timespec timeout); + int (*start_filtering) (struct dmx_ts_feed* feed); + int (*stop_filtering) (struct dmx_ts_feed* feed); +}; + +/*--------------------------------------------------------------------------*/ +/* Section reception */ +/*--------------------------------------------------------------------------*/ + +struct dmx_section_filter { + u8 filter_value [DMX_MAX_FILTER_SIZE]; + u8 filter_mask [DMX_MAX_FILTER_SIZE]; + u8 filter_mode [DMX_MAX_FILTER_SIZE]; + struct dmx_section_feed* parent; /* Back-pointer */ + void* priv; /* Pointer to private data of the API client */ +}; + +struct dmx_section_feed { + int is_filtering; /* Set to non-zero when filtering in progress */ + struct dmx_demux* parent; /* Back-pointer */ + void* priv; /* Pointer to private data of the API client */ + + int check_crc; + u32 crc_val; + + u8 *secbuf; + u8 secbuf_base[DMX_MAX_SECFEED_SIZE]; + u16 secbufp, seclen, tsfeedp; + + int (*set) (struct dmx_section_feed* feed, + u16 pid, + size_t circular_buffer_size, + int check_crc); + int (*allocate_filter) (struct dmx_section_feed* feed, + struct dmx_section_filter** filter); + int (*release_filter) (struct dmx_section_feed* feed, + struct dmx_section_filter* filter); + int (*start_filtering) (struct dmx_section_feed* feed); + int (*stop_filtering) (struct dmx_section_feed* feed); +}; + +/*--------------------------------------------------------------------------*/ +/* Callback functions */ +/*--------------------------------------------------------------------------*/ + +typedef int (*dmx_ts_cb) ( const u8 * buffer1, + size_t buffer1_length, + const u8 * buffer2, + size_t buffer2_length, + struct dmx_ts_feed* source, + enum dmx_success success); + +typedef int (*dmx_section_cb) ( const u8 * buffer1, + size_t buffer1_len, + const u8 * buffer2, + size_t buffer2_len, + struct dmx_section_filter * source, + enum dmx_success success); + +/*--------------------------------------------------------------------------*/ +/* DVB Front-End */ +/*--------------------------------------------------------------------------*/ + +enum dmx_frontend_source { + DMX_MEMORY_FE, + DMX_FRONTEND_0, + DMX_FRONTEND_1, + DMX_FRONTEND_2, + DMX_FRONTEND_3, + DMX_STREAM_0, /* external stream input, e.g. LVDS */ + DMX_STREAM_1, + DMX_STREAM_2, + DMX_STREAM_3 +}; + +struct dmx_frontend { + struct list_head connectivity_list; /* List of front-ends that can + be connected to a particular + demux */ + enum dmx_frontend_source source; +}; + +/*--------------------------------------------------------------------------*/ +/* MPEG-2 TS Demux */ +/*--------------------------------------------------------------------------*/ + +/* + * Flags OR'ed in the capabilities field of struct dmx_demux. + */ + +#define DMX_TS_FILTERING 1 +#define DMX_PES_FILTERING 2 +#define DMX_SECTION_FILTERING 4 +#define DMX_MEMORY_BASED_FILTERING 8 /* write() available */ +#define DMX_CRC_CHECKING 16 +#define DMX_TS_DESCRAMBLING 32 + +/* + * Demux resource type identifier. +*/ + +/* + * DMX_FE_ENTRY(): Casts elements in the list of registered + * front-ends from the generic type struct list_head + * to the type * struct dmx_frontend + *. +*/ + +#define DMX_FE_ENTRY(list) list_entry(list, struct dmx_frontend, connectivity_list) + +struct dmx_demux { + u32 capabilities; /* Bitfield of capability flags */ + struct dmx_frontend* frontend; /* Front-end connected to the demux */ + void* priv; /* Pointer to private data of the API client */ + int (*open) (struct dmx_demux* demux); + int (*close) (struct dmx_demux* demux); + int (*write) (struct dmx_demux* demux, const char __user *buf, size_t count); + int (*allocate_ts_feed) (struct dmx_demux* demux, + struct dmx_ts_feed** feed, + dmx_ts_cb callback); + int (*release_ts_feed) (struct dmx_demux* demux, + struct dmx_ts_feed* feed); + int (*allocate_section_feed) (struct dmx_demux* demux, + struct dmx_section_feed** feed, + dmx_section_cb callback); + int (*release_section_feed) (struct dmx_demux* demux, + struct dmx_section_feed* feed); + int (*add_frontend) (struct dmx_demux* demux, + struct dmx_frontend* frontend); + int (*remove_frontend) (struct dmx_demux* demux, + struct dmx_frontend* frontend); + struct list_head* (*get_frontends) (struct dmx_demux* demux); + int (*connect_frontend) (struct dmx_demux* demux, + struct dmx_frontend* frontend); + int (*disconnect_frontend) (struct dmx_demux* demux); + + int (*get_pes_pids) (struct dmx_demux* demux, u16 *pids); + + int (*get_caps) (struct dmx_demux* demux, struct dmx_caps *caps); + + int (*set_source) (struct dmx_demux* demux, const dmx_source_t *src); + + int (*get_stc) (struct dmx_demux* demux, unsigned int num, + u64 *stc, unsigned int *base); +}; + +#endif /* #ifndef __DEMUX_H */ diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c new file mode 100644 index 000000000000..889c9c16c6df --- /dev/null +++ b/drivers/media/dvb-core/dmxdev.c @@ -0,0 +1,1275 @@ +/* + * dmxdev.c - DVB demultiplexer device + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/ioctl.h> +#include <linux/wait.h> +#include <asm/uaccess.h> +#include "dmxdev.h" + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +#define dprintk if (debug) printk + +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("dmxdev: 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; + + dprintk("function : %s\n", __func__); + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + + if ((file->f_flags & O_ACCMODE) == O_RDWR) { + if (!(dmxdev->capabilities & DMXDEV_CAP_DUPLEX)) { + mutex_unlock(&dmxdev->mutex); + return -EOPNOTSUPP; + } + } + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + 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); + 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) { + dvbdev->readers++; + if (dmxdev->dvr_buffer.data) { + void *mem = dmxdev->dvr_buffer.data; + mb(); + spin_lock_irq(&dmxdev->lock); + dmxdev->dvr_buffer.data = NULL; + spin_unlock_irq(&dmxdev->lock); + vfree(mem); + } + } + /* TODO */ + dvbdev->users--; + if (dvbdev->users == 1 && dmxdev->exit == 1) { + fops_put(file->f_op); + file->f_op = NULL; + mutex_unlock(&dmxdev->mutex); + wake_up(&dvbdev->wait_queue); + } else + mutex_unlock(&dmxdev->mutex); + + return 0; +} + +static 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("function : %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(unsigned long data) +{ + struct dmxdev_filter *dmxdevfilter = (struct dmxdev_filter *)data; + + 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.function = dvb_dmxdev_filter_timeout; + dmxdevfilter->timer.data = (unsigned long)dmxdevfilter; + dmxdevfilter->timer.expires = + jiffies + 1 + (HZ / 2 + HZ * para->timeout) / 1000; + add_timer(&dmxdevfilter->timer); + } +} + +static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_section_filter *filter, + enum dmx_success success) +{ + struct dmxdev_filter *dmxdevfilter = filter->priv; + int ret; + + if (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("dmxdev: section callback %*ph\n", 6, buffer1); + 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) { + dvb_ringbuffer_flush(&dmxdevfilter->buffer); + 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, + enum dmx_success success) +{ + struct dmxdev_filter *dmxdevfilter = feed->priv; + struct dvb_ringbuffer *buffer; + 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; + else + buffer = &dmxdevfilter->dev->dvr_buffer; + 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) { + dvb_ringbuffer_flush(buffer); + 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) +{ + struct timespec timeout = { 0 }; + struct dmx_pes_filter_params *para = &filter->params.pes; + dmx_output_t otype; + int ret; + int ts_type; + dmx_pes_type_t 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, 32768, timeout); + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); + return ret; + } + + ret = tsfeed->start_filtering(tsfeed); + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); + return ret; + } + + return 0; +} + +static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) +{ + struct dmxdev *dmxdev = filter->dev; + struct dmxdev_feed *feed; + void *mem; + int ret, i; + + if (filter->state < DMXDEV_STATE_SET) + return -EINVAL; + + if (filter->state >= DMXDEV_STATE_GO) + dvb_dmxdev_filter_stop(filter); + + if (!filter->buffer.data) { + mem = vmalloc(filter->buffer.size); + if (!mem) + return -ENOMEM; + spin_lock_irq(&filter->dev->lock); + filter->buffer.data = mem; + spin_unlock_irq(&filter->dev->lock); + } + + dvb_ringbuffer_flush(&filter->buffer); + + switch (filter->type) { + case DMXDEV_TYPE_SEC: + { + struct dmx_sct_filter_params *para = &filter->params.sec; + struct dmx_section_filter **secfilter = &filter->filter.sec; + struct dmx_section_feed **secfeed = &filter->feed.sec; + + *secfilter = NULL; + *secfeed = NULL; + + + /* find active filter/feed with same PID */ + for (i = 0; i < dmxdev->filternum; i++) { + if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && + dmxdev->filter[i].type == DMXDEV_TYPE_SEC && + dmxdev->filter[i].params.sec.pid == para->pid) { + *secfeed = dmxdev->filter[i].feed.sec; + break; + } + } + + /* if no feed found, try to allocate new one */ + if (!*secfeed) { + ret = dmxdev->demux->allocate_section_feed(dmxdev->demux, + secfeed, + dvb_dmxdev_section_callback); + if (ret < 0) { + printk("DVB (%s): could not alloc feed\n", + __func__); + return ret; + } + + ret = (*secfeed)->set(*secfeed, para->pid, 32768, + (para->flags & DMX_CHECK_CRC) ? 1 : 0); + if (ret < 0) { + printk("DVB (%s): could not set feed\n", + __func__); + dvb_dmxdev_feed_restart(filter); + return ret; + } + } else { + dvb_dmxdev_feed_stop(filter); + } + + ret = (*secfeed)->allocate_filter(*secfeed, secfilter); + if (ret < 0) { + dvb_dmxdev_feed_restart(filter); + filter->feed.sec->start_filtering(*secfeed); + dprintk("could not get filter\n"); + return ret; + } + + (*secfilter)->priv = filter; + + memcpy(&((*secfilter)->filter_value[3]), + &(para->filter.filter[1]), DMX_FILTER_SIZE - 1); + memcpy(&(*secfilter)->filter_mask[3], + ¶->filter.mask[1], DMX_FILTER_SIZE - 1); + memcpy(&(*secfilter)->filter_mode[3], + ¶->filter.mode[1], DMX_FILTER_SIZE - 1); + + (*secfilter)->filter_value[0] = para->filter.filter[0]; + (*secfilter)->filter_mask[0] = para->filter.mask[0]; + (*secfilter)->filter_mode[0] = para->filter.mode[0]; + (*secfilter)->filter_mask[1] = 0; + (*secfilter)->filter_mask[2] = 0; + + filter->todo = 0; + + ret = filter->feed.sec->start_filtering(filter->feed.sec); + if (ret < 0) + return ret; + + dvb_dmxdev_filter_timer(filter); + break; + } + case DMXDEV_TYPE_PES: + list_for_each_entry(feed, &filter->feed.ts, next) { + ret = dvb_dmxdev_start_feed(dmxdev, filter, feed); + if (ret < 0) { + dvb_dmxdev_filter_stop(filter); + return ret; + } + } + break; + default: + return -EINVAL; + } + + dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); + return 0; +} + +static int dvb_demux_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + int i; + struct dmxdev_filter *dmxdevfilter; + + if (!dmxdev->filter) + return -EINVAL; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + for (i = 0; i < dmxdev->filternum; i++) + if (dmxdev->filter[i].state == DMXDEV_STATE_FREE) + break; + + if (i == dmxdev->filternum) { + mutex_unlock(&dmxdev->mutex); + return -EMFILE; + } + + dmxdevfilter = &dmxdev->filter[i]; + mutex_init(&dmxdevfilter->mutex); + file->private_data = dmxdevfilter; + + dvb_ringbuffer_init(&dmxdevfilter->buffer, NULL, 8192); + dmxdevfilter->type = DMXDEV_TYPE_NONE; + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); + init_timer(&dmxdevfilter->timer); + + 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); + + 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(dmx_filter_t *filter) +{ + int i; + + for (i = 0; i < DMX_FILTER_SIZE; i++) + filter->mode[i] ^= 0xff; +} + +static int dvb_dmxdev_add_pid(struct dmxdev *dmxdev, + struct dmxdev_filter *filter, u16 pid) +{ + struct dmxdev_feed *feed; + + 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("function : %s\n", __func__); + + 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 (params->pes_type > DMX_PES_OTHER || params->pes_type < 0) + 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_CAPS: + if (!dmxdev->demux->get_caps) { + ret = -EINVAL; + break; + } + ret = dmxdev->demux->get_caps(dmxdev->demux, parg); + break; + + case DMX_SET_SOURCE: + if (!dmxdev->demux->set_source) { + ret = -EINVAL; + break; + } + ret = dmxdev->demux->set_source(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; + + default: + ret = -EINVAL; + 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 unsigned int dvb_demux_poll(struct file *file, poll_table *wait) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + unsigned int mask = 0; + + if (!dmxdevfilter) + return -EINVAL; + + poll_wait(file, &dmxdevfilter->buffer.queue, wait); + + if (dmxdevfilter->state != DMXDEV_STATE_GO && + dmxdevfilter->state != DMXDEV_STATE_DONE && + dmxdevfilter->state != DMXDEV_STATE_TIMEDOUT) + return 0; + + if (dmxdevfilter->buffer.error) + mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); + + if (!dvb_ringbuffer_empty(&dmxdevfilter->buffer)) + mask |= (POLLIN | POLLRDNORM | POLLPRI); + + return mask; +} + +static int dvb_demux_release(struct inode *inode, struct file *file) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + struct dmxdev *dmxdev = dmxdevfilter->dev; + + int ret; + + ret = dvb_dmxdev_filter_free(dmxdev, dmxdevfilter); + + mutex_lock(&dmxdev->mutex); + dmxdev->dvbdev->users--; + if(dmxdev->dvbdev->users==1 && dmxdev->exit==1) { + fops_put(file->f_op); + file->f_op = NULL; + mutex_unlock(&dmxdev->mutex); + wake_up(&dmxdev->dvbdev->wait_queue); + } else + mutex_unlock(&dmxdev->mutex); + + return ret; +} + +static const struct file_operations dvb_demux_fops = { + .owner = THIS_MODULE, + .read = dvb_demux_read, + .unlocked_ioctl = dvb_demux_ioctl, + .open = dvb_demux_open, + .release = dvb_demux_release, + .poll = dvb_demux_poll, + .llseek = default_llseek, +}; + +static struct dvb_device dvbdev_demux = { + .priv = NULL, + .users = 1, + .writers = 1, + .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; + + default: + ret = -EINVAL; + 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 unsigned int dvb_dvr_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + unsigned int mask = 0; + + dprintk("function : %s\n", __func__); + + poll_wait(file, &dmxdev->dvr_buffer.queue, wait); + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if (dmxdev->dvr_buffer.error) + mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); + + if (!dvb_ringbuffer_empty(&dmxdev->dvr_buffer)) + mask |= (POLLIN | POLLRDNORM | POLLPRI); + } else + mask |= (POLLOUT | POLLWRNORM | POLLPRI); + + return mask; +} + +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, +}; + +static struct dvb_device dvbdev_dvr = { + .priv = NULL, + .readers = 1, + .users = 1, + .fops = &dvb_dvr_fops +}; + +int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) +{ + int i; + + if (dmxdev->demux->open(dmxdev->demux) < 0) + return -EUSERS; + + dmxdev->filter = vmalloc(dmxdev->filternum * sizeof(struct dmxdev_filter)); + if (!dmxdev->filter) + return -ENOMEM; + + mutex_init(&dmxdev->mutex); + spin_lock_init(&dmxdev->lock); + for (i = 0; i < dmxdev->filternum; i++) { + dmxdev->filter[i].dev = dmxdev; + dmxdev->filter[i].buffer.data = NULL; + dvb_dmxdev_filter_state_set(&dmxdev->filter[i], + DMXDEV_STATE_FREE); + } + + dvb_register_device(dvb_adapter, &dmxdev->dvbdev, &dvbdev_demux, dmxdev, + DVB_DEVICE_DEMUX); + dvb_register_device(dvb_adapter, &dmxdev->dvr_dvbdev, &dvbdev_dvr, + dmxdev, DVB_DEVICE_DVR); + + dvb_ringbuffer_init(&dmxdev->dvr_buffer, NULL, 8192); + + return 0; +} + +EXPORT_SYMBOL(dvb_dmxdev_init); + +void dvb_dmxdev_release(struct dmxdev *dmxdev) +{ + dmxdev->exit=1; + if (dmxdev->dvbdev->users > 1) { + wait_event(dmxdev->dvbdev->wait_queue, + dmxdev->dvbdev->users==1); + } + if (dmxdev->dvr_dvbdev->users > 1) { + wait_event(dmxdev->dvr_dvbdev->wait_queue, + dmxdev->dvr_dvbdev->users==1); + } + + dvb_unregister_device(dmxdev->dvbdev); + dvb_unregister_device(dmxdev->dvr_dvbdev); + + vfree(dmxdev->filter); + dmxdev->filter = NULL; + dmxdev->demux->close(dmxdev->demux); +} + +EXPORT_SYMBOL(dvb_dmxdev_release); diff --git a/drivers/media/dvb-core/dmxdev.h b/drivers/media/dvb-core/dmxdev.h new file mode 100644 index 000000000000..02ebe28f830d --- /dev/null +++ b/drivers/media/dvb-core/dmxdev.h @@ -0,0 +1,118 @@ +/* + * dmxdev.h + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DMXDEV_H_ +#define _DMXDEV_H_ + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <linux/dvb/dmx.h> + +#include "dvbdev.h" +#include "demux.h" +#include "dvb_ringbuffer.h" + +enum dmxdev_type { + DMXDEV_TYPE_NONE, + DMXDEV_TYPE_SEC, + DMXDEV_TYPE_PES, +}; + +enum dmxdev_state { + DMXDEV_STATE_FREE, + DMXDEV_STATE_ALLOCATED, + DMXDEV_STATE_SET, + DMXDEV_STATE_GO, + DMXDEV_STATE_DONE, + DMXDEV_STATE_TIMEDOUT +}; + +struct dmxdev_feed { + u16 pid; + struct dmx_ts_feed *ts; + struct list_head next; +}; + +struct dmxdev_filter { + union { + struct dmx_section_filter *sec; + } filter; + + union { + /* list of TS and PES feeds (struct dmxdev_feed) */ + struct list_head ts; + struct dmx_section_feed *sec; + } feed; + + union { + struct dmx_sct_filter_params sec; + struct dmx_pes_filter_params pes; + } params; + + enum dmxdev_type type; + enum dmxdev_state state; + struct dmxdev *dev; + struct dvb_ringbuffer buffer; + + struct mutex mutex; + + /* only for sections */ + struct timer_list timer; + int todo; + u8 secheader[3]; +}; + + +struct dmxdev { + struct dvb_device *dvbdev; + struct dvb_device *dvr_dvbdev; + + struct dmxdev_filter *filter; + struct dmx_demux *demux; + + int filternum; + int capabilities; + + unsigned int exit:1; +#define DMXDEV_CAP_DUPLEX 1 + struct dmx_frontend *dvr_orig_fe; + + struct dvb_ringbuffer dvr_buffer; +#define DVR_BUFFER_SIZE (10*188*1024) + + struct mutex mutex; + spinlock_t lock; +}; + + +int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *); +void dvb_dmxdev_release(struct dmxdev *dmxdev); + +#endif /* _DMXDEV_H_ */ diff --git a/drivers/media/dvb-core/dvb-usb-ids.h b/drivers/media/dvb-core/dvb-usb-ids.h new file mode 100644 index 000000000000..26c44818a5ab --- /dev/null +++ b/drivers/media/dvb-core/dvb-usb-ids.h @@ -0,0 +1,361 @@ +/* dvb-usb-ids.h is part of the DVB USB library. + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) see + * dvb-usb-init.c for copyright information. + * + * a header file containing define's for the USB device supported by the + * various drivers. + */ +#ifndef _DVB_USB_IDS_H_ +#define _DVB_USB_IDS_H_ + +/* Vendor IDs */ +#define USB_VID_ADSTECH 0x06e1 +#define USB_VID_AFATECH 0x15a4 +#define USB_VID_ALCOR_MICRO 0x058f +#define USB_VID_ALINK 0x05e3 +#define USB_VID_AMT 0x1c73 +#define USB_VID_ANCHOR 0x0547 +#define USB_VID_ANSONIC 0x10b9 +#define USB_VID_ANUBIS_ELECTRONIC 0x10fd +#define USB_VID_ASUS 0x0b05 +#define USB_VID_AVERMEDIA 0x07ca +#define USB_VID_COMPRO 0x185b +#define USB_VID_COMPRO_UNK 0x145f +#define USB_VID_CONEXANT 0x0572 +#define USB_VID_CYPRESS 0x04b4 +#define USB_VID_DIBCOM 0x10b8 +#define USB_VID_DPOSH 0x1498 +#define USB_VID_DVICO 0x0fe9 +#define USB_VID_E3C 0x18b4 +#define USB_VID_ELGATO 0x0fd9 +#define USB_VID_EMPIA 0xeb1a +#define USB_VID_GENPIX 0x09c0 +#define USB_VID_GRANDTEC 0x5032 +#define USB_VID_GTEK 0x1f4d +#define USB_VID_HANFTEK 0x15f4 +#define USB_VID_HAUPPAUGE 0x2040 +#define USB_VID_HYPER_PALTEK 0x1025 +#define USB_VID_INTEL 0x8086 +#define USB_VID_ITETECH 0x048d +#define USB_VID_KWORLD 0xeb2a +#define USB_VID_KWORLD_2 0x1b80 +#define USB_VID_KYE 0x0458 +#define USB_VID_LEADTEK 0x0413 +#define USB_VID_LITEON 0x04ca +#define USB_VID_MEDION 0x1660 +#define USB_VID_MIGLIA 0x18f3 +#define USB_VID_MSI 0x0db0 +#define USB_VID_MSI_2 0x1462 +#define USB_VID_OPERA1 0x695c +#define USB_VID_PINNACLE 0x2304 +#define USB_VID_PCTV 0x2013 +#define USB_VID_PIXELVIEW 0x1554 +#define USB_VID_REALTEK 0x0bda +#define USB_VID_TECHNOTREND 0x0b48 +#define USB_VID_TERRATEC 0x0ccd +#define USB_VID_TELESTAR 0x10b9 +#define USB_VID_VISIONPLUS 0x13d3 +#define USB_VID_SONY 0x1415 +#define USB_VID_TWINHAN 0x1822 +#define USB_VID_ULTIMA_ELECTRONIC 0x05d8 +#define USB_VID_UNIWILL 0x1584 +#define USB_VID_WIDEVIEW 0x14aa +#define USB_VID_GIGABYTE 0x1044 +#define USB_VID_YUAN 0x1164 +#define USB_VID_XTENSIONS 0x1ae7 +#define USB_VID_HUMAX_COEX 0x10b9 +#define USB_VID_774 0x7a69 +#define USB_VID_EVOLUTEPC 0x1e59 +#define USB_VID_AZUREWAVE 0x13d3 +#define USB_VID_TECHNISAT 0x14f7 + +/* Product IDs */ +#define USB_PID_ADSTECH_USB2_COLD 0xa333 +#define USB_PID_ADSTECH_USB2_WARM 0xa334 +#define USB_PID_AFATECH_AF9005 0x9020 +#define USB_PID_AFATECH_AF9015_9015 0x9015 +#define USB_PID_AFATECH_AF9015_9016 0x9016 +#define USB_PID_AFATECH_AF9035_1000 0x1000 +#define USB_PID_AFATECH_AF9035_1001 0x1001 +#define USB_PID_AFATECH_AF9035_1002 0x1002 +#define USB_PID_AFATECH_AF9035_1003 0x1003 +#define USB_PID_AFATECH_AF9035_9035 0x9035 +#define USB_PID_TREKSTOR_DVBT 0x901b +#define USB_VID_ALINK_DTU 0xf170 +#define USB_PID_ANSONIC_DVBT_USB 0x6000 +#define USB_PID_ANYSEE 0x861f +#define USB_PID_AZUREWAVE_AD_TU700 0x3237 +#define USB_PID_AZUREWAVE_6007 0x0ccd +#define USB_PID_AVERMEDIA_DVBT_USB_COLD 0x0001 +#define USB_PID_AVERMEDIA_DVBT_USB_WARM 0x0002 +#define USB_PID_AVERMEDIA_DVBT_USB2_COLD 0xa800 +#define USB_PID_AVERMEDIA_DVBT_USB2_WARM 0xa801 +#define USB_PID_COMPRO_DVBU2000_COLD 0xd000 +#define USB_PID_COMPRO_DVBU2000_WARM 0xd001 +#define USB_PID_COMPRO_DVBU2000_UNK_COLD 0x010c +#define USB_PID_COMPRO_DVBU2000_UNK_WARM 0x010d +#define USB_PID_COMPRO_VIDEOMATE_U500 0x1e78 +#define USB_PID_COMPRO_VIDEOMATE_U500_PC 0x1e80 +#define USB_PID_CONCEPTRONIC_CTVDIGRCU 0xe397 +#define USB_PID_CONEXANT_D680_DMB 0x86d6 +#define USB_PID_CREATIX_CTX1921 0x1921 +#define USB_PID_DELOCK_USB2_DVBT 0xb803 +#define USB_PID_DIBCOM_HOOK_DEFAULT 0x0064 +#define USB_PID_DIBCOM_HOOK_DEFAULT_REENUM 0x0065 +#define USB_PID_DIBCOM_MOD3000_COLD 0x0bb8 +#define USB_PID_DIBCOM_MOD3000_WARM 0x0bb9 +#define USB_PID_DIBCOM_MOD3001_COLD 0x0bc6 +#define USB_PID_DIBCOM_MOD3001_WARM 0x0bc7 +#define USB_PID_DIBCOM_STK7700P 0x1e14 +#define USB_PID_DIBCOM_STK7700P_PC 0x1e78 +#define USB_PID_DIBCOM_STK7700D 0x1ef0 +#define USB_PID_DIBCOM_STK7700_U7000 0x7001 +#define USB_PID_DIBCOM_STK7070P 0x1ebc +#define USB_PID_DIBCOM_STK7070PD 0x1ebe +#define USB_PID_DIBCOM_STK807XP 0x1f90 +#define USB_PID_DIBCOM_STK807XPVR 0x1f98 +#define USB_PID_DIBCOM_STK8096GP 0x1fa0 +#define USB_PID_DIBCOM_NIM8096MD 0x1fa8 +#define USB_PID_DIBCOM_TFE8096P 0x1f9C +#define USB_PID_DIBCOM_ANCHOR_2135_COLD 0x2131 +#define USB_PID_DIBCOM_STK7770P 0x1e80 +#define USB_PID_DIBCOM_NIM7090 0x1bb2 +#define USB_PID_DIBCOM_TFE7090PVR 0x1bb4 +#define USB_PID_DIBCOM_TFE7090E 0x1bb7 +#define USB_PID_DIBCOM_TFE7790E 0x1e6e +#define USB_PID_DIBCOM_NIM9090M 0x2383 +#define USB_PID_DIBCOM_NIM9090MD 0x2384 +#define USB_PID_DPOSH_M9206_COLD 0x9206 +#define USB_PID_DPOSH_M9206_WARM 0xa090 +#define USB_PID_E3C_EC168 0x1689 +#define USB_PID_E3C_EC168_2 0xfffa +#define USB_PID_E3C_EC168_3 0xfffb +#define USB_PID_E3C_EC168_4 0x1001 +#define USB_PID_E3C_EC168_5 0x1002 +#define USB_PID_FREECOM_DVBT 0x0160 +#define USB_PID_FREECOM_DVBT_2 0x0161 +#define USB_PID_UNIWILL_STK7700P 0x6003 +#define USB_PID_GENIUS_TVGO_DVB_T03 0x4012 +#define USB_PID_GRANDTEC_DVBT_USB_COLD 0x0fa0 +#define USB_PID_GRANDTEC_DVBT_USB_WARM 0x0fa1 +#define USB_PID_INTEL_CE9500 0x9500 +#define USB_PID_ITETECH_IT9135 0x9135 +#define USB_PID_ITETECH_IT9135_9005 0x9005 +#define USB_PID_ITETECH_IT9135_9006 0x9006 +#define USB_PID_KWORLD_399U 0xe399 +#define USB_PID_KWORLD_399U_2 0xe400 +#define USB_PID_KWORLD_395U 0xe396 +#define USB_PID_KWORLD_395U_2 0xe39b +#define USB_PID_KWORLD_395U_3 0xe395 +#define USB_PID_KWORLD_395U_4 0xe39a +#define USB_PID_KWORLD_MC810 0xc810 +#define USB_PID_KWORLD_PC160_2T 0xc160 +#define USB_PID_KWORLD_PC160_T 0xc161 +#define USB_PID_KWORLD_UB383_T 0xe383 +#define USB_PID_KWORLD_UB499_2T_T09 0xe409 +#define USB_PID_KWORLD_VSTREAM_COLD 0x17de +#define USB_PID_KWORLD_VSTREAM_WARM 0x17df +#define USB_PID_TERRATEC_CINERGY_T_USB_XE 0x0055 +#define USB_PID_TERRATEC_CINERGY_T_USB_XE_REV2 0x0069 +#define USB_PID_TERRATEC_CINERGY_T_STICK 0x0093 +#define USB_PID_TERRATEC_CINERGY_T_STICK_RC 0x0097 +#define USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC 0x0099 +#define USB_PID_TERRATEC_CINERGY_T_STICK_BLACK_REV1 0x00a9 +#define USB_PID_TWINHAN_VP7041_COLD 0x3201 +#define USB_PID_TWINHAN_VP7041_WARM 0x3202 +#define USB_PID_TWINHAN_VP7020_COLD 0x3203 +#define USB_PID_TWINHAN_VP7020_WARM 0x3204 +#define USB_PID_TWINHAN_VP7045_COLD 0x3205 +#define USB_PID_TWINHAN_VP7045_WARM 0x3206 +#define USB_PID_TWINHAN_VP7021_COLD 0x3207 +#define USB_PID_TWINHAN_VP7021_WARM 0x3208 +#define USB_PID_TINYTWIN 0x3226 +#define USB_PID_TINYTWIN_2 0xe402 +#define USB_PID_TINYTWIN_3 0x9016 +#define USB_PID_DNTV_TINYUSB2_COLD 0x3223 +#define USB_PID_DNTV_TINYUSB2_WARM 0x3224 +#define USB_PID_ULTIMA_TVBOX_COLD 0x8105 +#define USB_PID_ULTIMA_TVBOX_WARM 0x8106 +#define USB_PID_ULTIMA_TVBOX_AN2235_COLD 0x8107 +#define USB_PID_ULTIMA_TVBOX_AN2235_WARM 0x8108 +#define USB_PID_ULTIMA_TVBOX_ANCHOR_COLD 0x2235 +#define USB_PID_ULTIMA_TVBOX_USB2_COLD 0x8109 +#define USB_PID_ULTIMA_TVBOX_USB2_WARM 0x810a +#define USB_PID_ARTEC_T14_COLD 0x810b +#define USB_PID_ARTEC_T14_WARM 0x810c +#define USB_PID_ARTEC_T14BR 0x810f +#define USB_PID_ULTIMA_TVBOX_USB2_FX_COLD 0x8613 +#define USB_PID_ULTIMA_TVBOX_USB2_FX_WARM 0x1002 +#define USB_PID_UNK_HYPER_PALTEK_COLD 0x005e +#define USB_PID_UNK_HYPER_PALTEK_WARM 0x005f +#define USB_PID_HANFTEK_UMT_010_COLD 0x0001 +#define USB_PID_HANFTEK_UMT_010_WARM 0x0015 +#define USB_PID_DTT200U_COLD 0x0201 +#define USB_PID_DTT200U_WARM 0x0301 +#define USB_PID_WT220U_ZAP250_COLD 0x0220 +#define USB_PID_WT220U_COLD 0x0222 +#define USB_PID_WT220U_WARM 0x0221 +#define USB_PID_WT220U_FC_COLD 0x0225 +#define USB_PID_WT220U_FC_WARM 0x0226 +#define USB_PID_WT220U_ZL0353_COLD 0x022a +#define USB_PID_WT220U_ZL0353_WARM 0x022b +#define USB_PID_WINTV_NOVA_T_USB2_COLD 0x9300 +#define USB_PID_WINTV_NOVA_T_USB2_WARM 0x9301 +#define USB_PID_HAUPPAUGE_NOVA_T_500 0x9941 +#define USB_PID_HAUPPAUGE_NOVA_T_500_2 0x9950 +#define USB_PID_HAUPPAUGE_NOVA_T_500_3 0x8400 +#define USB_PID_HAUPPAUGE_NOVA_T_STICK 0x7050 +#define USB_PID_HAUPPAUGE_NOVA_T_STICK_2 0x7060 +#define USB_PID_HAUPPAUGE_NOVA_T_STICK_3 0x7070 +#define USB_PID_HAUPPAUGE_MYTV_T 0x7080 +#define USB_PID_HAUPPAUGE_NOVA_TD_STICK 0x9580 +#define USB_PID_HAUPPAUGE_NOVA_TD_STICK_52009 0x5200 +#define USB_PID_HAUPPAUGE_TIGER_ATSC 0xb200 +#define USB_PID_HAUPPAUGE_TIGER_ATSC_B210 0xb210 +#define USB_PID_AVERMEDIA_EXPRESS 0xb568 +#define USB_PID_AVERMEDIA_VOLAR 0xa807 +#define USB_PID_AVERMEDIA_VOLAR_2 0xb808 +#define USB_PID_AVERMEDIA_VOLAR_A868R 0xa868 +#define USB_PID_AVERMEDIA_MCE_USB_M038 0x1228 +#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R 0x0039 +#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R_ATSC 0x1039 +#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R_DVBT 0x2039 +#define USB_PID_AVERMEDIA_VOLAR_X 0xa815 +#define USB_PID_AVERMEDIA_VOLAR_X_2 0x8150 +#define USB_PID_AVERMEDIA_A309 0xa309 +#define USB_PID_AVERMEDIA_A310 0xa310 +#define USB_PID_AVERMEDIA_A850 0x850a +#define USB_PID_AVERMEDIA_A850T 0x850b +#define USB_PID_AVERMEDIA_A805 0xa805 +#define USB_PID_AVERMEDIA_A815M 0x815a +#define USB_PID_AVERMEDIA_A835 0xa835 +#define USB_PID_AVERMEDIA_B835 0xb835 +#define USB_PID_AVERMEDIA_1867 0x1867 +#define USB_PID_AVERMEDIA_A867 0xa867 +#define USB_PID_AVERMEDIA_TWINSTAR 0x0825 +#define USB_PID_TECHNOTREND_CONNECT_S2400 0x3006 +#define USB_PID_TECHNOTREND_CONNECT_CT3650 0x300d +#define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY 0x005a +#define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY_2 0x0081 +#define USB_PID_TERRATEC_CINERGY_HT_USB_XE 0x0058 +#define USB_PID_TERRATEC_CINERGY_HT_EXPRESS 0x0060 +#define USB_PID_TERRATEC_CINERGY_T_EXPRESS 0x0062 +#define USB_PID_TERRATEC_CINERGY_T_XXS 0x0078 +#define USB_PID_TERRATEC_CINERGY_T_XXS_2 0x00ab +#define USB_PID_TERRATEC_H7 0x10b4 +#define USB_PID_TERRATEC_H7_2 0x10a3 +#define USB_PID_TERRATEC_T3 0x10a0 +#define USB_PID_TERRATEC_T5 0x10a1 +#define USB_PID_NOXON_DAB_STICK 0x00b3 +#define USB_PID_PINNACLE_EXPRESSCARD_320CX 0x022e +#define USB_PID_PINNACLE_PCTV2000E 0x022c +#define USB_PID_PINNACLE_PCTV_DVB_T_FLASH 0x0228 +#define USB_PID_PINNACLE_PCTV_DUAL_DIVERSITY_DVB_T 0x0229 +#define USB_PID_PINNACLE_PCTV71E 0x022b +#define USB_PID_PINNACLE_PCTV72E 0x0236 +#define USB_PID_PINNACLE_PCTV73E 0x0237 +#define USB_PID_PINNACLE_PCTV310E 0x3211 +#define USB_PID_PINNACLE_PCTV801E 0x023a +#define USB_PID_PINNACLE_PCTV801E_SE 0x023b +#define USB_PID_PINNACLE_PCTV340E 0x023d +#define USB_PID_PINNACLE_PCTV340E_SE 0x023e +#define USB_PID_PINNACLE_PCTV73A 0x0243 +#define USB_PID_PINNACLE_PCTV73ESE 0x0245 +#define USB_PID_PINNACLE_PCTV74E 0x0246 +#define USB_PID_PINNACLE_PCTV282E 0x0248 +#define USB_PID_PIXELVIEW_SBTVD 0x5010 +#define USB_PID_PCTV_200E 0x020e +#define USB_PID_PCTV_400E 0x020f +#define USB_PID_PCTV_450E 0x0222 +#define USB_PID_PCTV_452E 0x021f +#define USB_PID_REALTEK_RTL2831U 0x2831 +#define USB_PID_REALTEK_RTL2832U 0x2832 +#define USB_PID_TECHNOTREND_CONNECT_S2_3600 0x3007 +#define USB_PID_TECHNOTREND_CONNECT_S2_3650_CI 0x300a +#define USB_PID_NEBULA_DIGITV 0x0201 +#define USB_PID_DVICO_BLUEBIRD_LGDT 0xd820 +#define USB_PID_DVICO_BLUEBIRD_LG064F_COLD 0xd500 +#define USB_PID_DVICO_BLUEBIRD_LG064F_WARM 0xd501 +#define USB_PID_DVICO_BLUEBIRD_LGZ201_COLD 0xdb00 +#define USB_PID_DVICO_BLUEBIRD_LGZ201_WARM 0xdb01 +#define USB_PID_DVICO_BLUEBIRD_TH7579_COLD 0xdb10 +#define USB_PID_DVICO_BLUEBIRD_TH7579_WARM 0xdb11 +#define USB_PID_DVICO_BLUEBIRD_DUAL_1_COLD 0xdb50 +#define USB_PID_DVICO_BLUEBIRD_DUAL_1_WARM 0xdb51 +#define USB_PID_DVICO_BLUEBIRD_DUAL_2_COLD 0xdb58 +#define USB_PID_DVICO_BLUEBIRD_DUAL_2_WARM 0xdb59 +#define USB_PID_DVICO_BLUEBIRD_DUAL_4 0xdb78 +#define USB_PID_DVICO_BLUEBIRD_DUAL_4_REV_2 0xdb98 +#define USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2 0xdb70 +#define USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM 0xdb71 +#define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_COLD 0xdb54 +#define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_WARM 0xdb55 +#define USB_PID_MEDION_MD95700 0x0932 +#define USB_PID_MSI_MEGASKY580 0x5580 +#define USB_PID_MSI_MEGASKY580_55801 0x5581 +#define USB_PID_KYE_DVB_T_COLD 0x701e +#define USB_PID_KYE_DVB_T_WARM 0x701f +#define USB_PID_LITEON_DVB_T_COLD 0xf000 +#define USB_PID_LITEON_DVB_T_WARM 0xf001 +#define USB_PID_DIGIVOX_MINI_SL_COLD 0xe360 +#define USB_PID_DIGIVOX_MINI_SL_WARM 0xe361 +#define USB_PID_GRANDTEC_DVBT_USB2_COLD 0x0bc6 +#define USB_PID_GRANDTEC_DVBT_USB2_WARM 0x0bc7 +#define USB_PID_WINFAST_DTV2000DS 0x6a04 +#define USB_PID_WINFAST_DTV_DONGLE_COLD 0x6025 +#define USB_PID_WINFAST_DTV_DONGLE_WARM 0x6026 +#define USB_PID_WINFAST_DTV_DONGLE_STK7700P 0x6f00 +#define USB_PID_WINFAST_DTV_DONGLE_H 0x60f6 +#define USB_PID_WINFAST_DTV_DONGLE_STK7700P_2 0x6f01 +#define USB_PID_WINFAST_DTV_DONGLE_GOLD 0x6029 +#define USB_PID_GENPIX_8PSK_REV_1_COLD 0x0200 +#define USB_PID_GENPIX_8PSK_REV_1_WARM 0x0201 +#define USB_PID_GENPIX_8PSK_REV_2 0x0202 +#define USB_PID_GENPIX_SKYWALKER_1 0x0203 +#define USB_PID_GENPIX_SKYWALKER_CW3K 0x0204 +#define USB_PID_GENPIX_SKYWALKER_2 0x0206 +#define USB_PID_SIGMATEK_DVB_110 0x6610 +#define USB_PID_MSI_DIGI_VOX_MINI_II 0x1513 +#define USB_PID_MSI_DIGIVOX_DUO 0x8801 +#define USB_PID_OPERA1_COLD 0x2830 +#define USB_PID_OPERA1_WARM 0x3829 +#define USB_PID_LIFEVIEW_TV_WALKER_TWIN_COLD 0x0514 +#define USB_PID_LIFEVIEW_TV_WALKER_TWIN_WARM 0x0513 +#define USB_PID_GIGABYTE_U7000 0x7001 +#define USB_PID_GIGABYTE_U8000 0x7002 +#define USB_PID_ASUS_U3000 0x171f +#define USB_PID_ASUS_U3000H 0x1736 +#define USB_PID_ASUS_U3100 0x173f +#define USB_PID_YUAN_EC372S 0x1edc +#define USB_PID_YUAN_STK7700PH 0x1f08 +#define USB_PID_YUAN_PD378S 0x2edc +#define USB_PID_YUAN_MC770 0x0871 +#define USB_PID_YUAN_STK7700D 0x1efc +#define USB_PID_YUAN_STK7700D_2 0x1e8c +#define USB_PID_DW2102 0x2102 +#define USB_PID_XTENSIONS_XD_380 0x0381 +#define USB_PID_TELESTAR_STARSTICK_2 0x8000 +#define USB_PID_MSI_DIGI_VOX_MINI_III 0x8807 +#define USB_PID_SONY_PLAYTV 0x0003 +#define USB_PID_MYGICA_D689 0xd811 +#define USB_PID_ELGATO_EYETV_DIVERSITY 0x0011 +#define USB_PID_ELGATO_EYETV_DTT 0x0021 +#define USB_PID_ELGATO_EYETV_DTT_2 0x003f +#define USB_PID_ELGATO_EYETV_DTT_Dlx 0x0020 +#define USB_PID_ELGATO_EYETV_SAT 0x002a +#define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_COLD 0x5000 +#define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_WARM 0x5001 +#define USB_PID_FRIIO_WHITE 0x0001 +#define USB_PID_TVWAY_PLUS 0x0002 +#define USB_PID_SVEON_STV20 0xe39d +#define USB_PID_SVEON_STV22 0xe401 +#define USB_PID_SVEON_STV22_IT9137 0xe411 +#define USB_PID_AZUREWAVE_AZ6027 0x3275 +#define USB_PID_TERRATEC_DVBS2CI_V1 0x10a4 +#define USB_PID_TERRATEC_DVBS2CI_V2 0x10ac +#define USB_PID_TECHNISAT_USB2_HDCI_V1 0x0001 +#define USB_PID_TECHNISAT_USB2_HDCI_V2 0x0002 +#define USB_PID_TECHNISAT_AIRSTAR_TELESTICK_2 0x0004 +#define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 +#endif 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 000000000000..9be65a3b931f --- /dev/null +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -0,0 +1,1753 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/sched.h> +#include <linux/kthread.h> + +#include "dvb_ca_en50221.h" +#include "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 if (dvb_ca_en50221_debug) printk + +#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 STATUSREG_TXERR (STATUSREG_RE|STATUSREG_WE) /* general transfer error */ + + +#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 { + + /* 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; +}; + +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); + + +/** + * Safely find needle in haystack. + * + * @param haystack Buffer to look in. + * @param hlen Number of bytes in haystack. + * @param needle Buffer to find. + * @param 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 */ + + +/** + * Check CAM status. + */ +static int dvb_ca_en50221_check_camstatus(struct dvb_ca_private *ca, int 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(&ca->slot_info[slot].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 = (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_NONE); + cam_changed = (cam_present_now != cam_present_old); + } + + if (cam_changed) { + if (!cam_present_now) { + ca->slot_info[slot].camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED; + } else { + ca->slot_info[slot].camchange_type = DVB_CA_EN50221_CAMCHANGE_INSERTED; + } + atomic_set(&ca->slot_info[slot].camchange_count, 1); + } else { + if ((ca->slot_info[slot].slot_state == DVB_CA_SLOTSTATE_WAITREADY) && + (slot_status & DVB_CA_EN50221_POLL_CAM_READY)) { + // move to validate state if reset is completed + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_VALIDATE; + } + } + + return cam_changed; +} + + +/** + * Wait for flags to become set on the STATUS register on a CAM interface, + * checking for errors and timeout. + * + * @param ca CA instance. + * @param slot Slot on interface. + * @param waitfor Flags to wait for. + * @param timeout_ms 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) { + /* read the status and check for error */ + int 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 */ + msleep(1); + } + + dprintk("%s failed timeout:%lu\n", __func__, jiffies - start); + + /* if we get here, we've timed out */ + return -ETIMEDOUT; +} + + +/** + * Initialise the link layer connection to a CAM. + * + * @param ca CA instance. + * @param slot Slot id. + * + * @return 0 on success, nonzero on failure. + */ +static int dvb_ca_en50221_link_init(struct dvb_ca_private *ca, int slot) +{ + int ret; + int buf_size; + u8 buf[2]; + + dprintk("%s\n", __func__); + + /* we'll be determining these during this function */ + ca->slot_info[slot].da_irq_supported = 0; + + /* set the host link buffer size temporarily. it will be overwritten with the + * real negotiated size later. */ + ca->slot_info[slot].link_buf_size = 2; + + /* read the buffer size from the CAM */ + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN | CMDREG_SR)) != 0) + return ret; + if ((ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_DA, HZ / 10)) != 0) + return ret; + if ((ret = dvb_ca_en50221_read_data(ca, slot, buf, 2)) != 2) + return -EIO; + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN)) != 0) + 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; + ca->slot_info[slot].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 */ + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN | CMDREG_SW)) != 0) + return ret; + if ((ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_FR, HZ / 10)) != 0) + return ret; + if ((ret = dvb_ca_en50221_write_data(ca, slot, buf, 2)) != 2) + return -EIO; + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN)) != 0) + return ret; + + /* success */ + return 0; +} + +/** + * Read a tuple from attribute memory. + * + * @param ca CA instance. + * @param slot Slot id. + * @param address Address to read from. Updated. + * @param tupleType Tuple id byte. Updated. + * @param tupleLength Tuple length. Updated. + * @param 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 *tupleType, int *tupleLength, u8 * tuple) +{ + int i; + int _tupleType; + int _tupleLength; + int _address = *address; + + /* grab the next tuple length and type */ + if ((_tupleType = ca->pub->read_attribute_mem(ca->pub, slot, _address)) < 0) + return _tupleType; + if (_tupleType == 0xff) { + dprintk("END OF CHAIN TUPLE type:0x%x\n", _tupleType); + *address += 2; + *tupleType = _tupleType; + *tupleLength = 0; + return 0; + } + if ((_tupleLength = ca->pub->read_attribute_mem(ca->pub, slot, _address + 2)) < 0) + return _tupleLength; + _address += 4; + + dprintk("TUPLE type:0x%x length:%i\n", _tupleType, _tupleLength); + + /* read in the whole tuple */ + for (i = 0; i < _tupleLength; 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 += (_tupleLength * 2); + + // success + *tupleType = _tupleType; + *tupleLength = _tupleLength; + *address = _address; + return 0; +} + + +/** + * Parse attribute memory of a CAM module, extracting Config register, and checking + * it is a DVB CAM module. + * + * @param ca CA instance. + * @param slot Slot id. + * + * @return 0 on success, <0 on failure. + */ +static int dvb_ca_en50221_parse_attributes(struct dvb_ca_private *ca, int slot) +{ + int address = 0; + int tupleLength; + int tupleType; + 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 + if ((status = + dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x1D) + return -EINVAL; + + + + // CISTPL_DEVICE_0C + if ((status = + dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x1C) + return -EINVAL; + + + + // CISTPL_VERS_1 + if ((status = + dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x15) + return -EINVAL; + + + + // CISTPL_MANFID + if ((status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, + &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x20) + return -EINVAL; + if (tupleLength != 4) + return -EINVAL; + manfid = (tuple[1] << 8) | tuple[0]; + devid = (tuple[3] << 8) | tuple[2]; + + + + // CISTPL_CONFIG + if ((status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, + &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x1A) + return -EINVAL; + if (tupleLength < 3) + return -EINVAL; + + /* extract the configbase */ + rasz = tuple[0] & 3; + if (tupleLength < (3 + rasz + 14)) + return -EINVAL; + ca->slot_info[slot].config_base = 0; + for (i = 0; i < rasz + 1; i++) { + ca->slot_info[slot].config_base |= (tuple[2 + i] << (8 * i)); + } + + /* check it contains the correct DVB string */ + dvb_str = findstr((char *)tuple, tupleLength, "DVB_CI_V", 8); + if (dvb_str == NULL) + return -EINVAL; + if (tupleLength < ((dvb_str - (char *) tuple) + 12)) + return -EINVAL; + + /* is it a version we support? */ + if (strncmp(dvb_str + 8, "1.00", 4)) { + printk("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)) { + if ((status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, + &tupleLength, tuple)) < 0) + return status; + switch (tupleType) { + case 0x1B: // CISTPL_CFTABLE_ENTRY + if (tupleLength < (2 + 11 + 17)) + break; + + /* if we've already parsed one, just use it */ + if (got_cftableentry) + break; + + /* get the config option */ + ca->slot_info[slot].config_option = tuple[0] & 0x3f; + + /* OK, check it contains the correct strings */ + if ((findstr((char *)tuple, tupleLength, "DVB_HOST", 8) == NULL) || + (findstr((char *)tuple, tupleLength, "DVB_CI_MODULE", 13) == NULL)) + 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 and move to the next one */ + dprintk("dvb_ca: Skipping unknown tuple type:0x%x length:0x%x\n", tupleType, + tupleLength); + 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, ca->slot_info[slot].config_base, ca->slot_info[slot].config_option); + + // success! + return 0; +} + + +/** + * Set CAM's configoption correctly. + * + * @param ca CA instance. + * @param slot Slot containing the CAM. + */ +static int dvb_ca_en50221_set_configoption(struct dvb_ca_private *ca, int slot) +{ + int configoption; + + dprintk("%s\n", __func__); + + /* set the config option */ + ca->pub->write_attribute_mem(ca->pub, slot, + ca->slot_info[slot].config_base, + ca->slot_info[slot].config_option); + + /* check it */ + configoption = ca->pub->read_attribute_mem(ca->pub, slot, ca->slot_info[slot].config_base); + dprintk("Set configoption 0x%x, read configoption 0x%x\n", + ca->slot_info[slot].config_option, configoption & 0x3f); + + /* fine! */ + return 0; + +} + + +/** + * 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. + * + * @param ca CA instance. + * @param slot Slot to read from. + * @param 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. + * @param 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) +{ + 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 == NULL) { + int buf_free; + + if (ca->slot_info[slot].rx_buffer.data == NULL) { + status = -EIO; + goto exit; + } + buf_free = dvb_ringbuffer_free(&ca->slot_info[slot].rx_buffer); + + if (buf_free < (ca->slot_info[slot].link_buf_size + DVB_RINGBUFFER_PKTHDRSIZE)) { + status = -EAGAIN; + goto exit; + } + } + + /* check if there is data available */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (!(status & STATUSREG_DA)) { + /* no data */ + status = 0; + goto exit; + } + + /* read the amount of data */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_SIZE_HIGH)) < 0) + goto exit; + bytes_read = status << 8; + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_SIZE_LOW)) < 0) + goto exit; + bytes_read |= status; + + /* check it will fit */ + if (ebuf == NULL) { + if (bytes_read > ca->slot_info[slot].link_buf_size) { + printk("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, ca->slot_info[slot].link_buf_size); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + if (bytes_read < 2) { + printk("dvb_ca adapter %d: CAM sent a buffer that was less than 2 bytes!\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + } else { + if (bytes_read > ecount) { + printk("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 */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_DATA)) < 0) + goto exit; + + /* OK, store it in the buffer */ + buf[i] = status; + } + + /* check for read error (RE should now be 0) */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (status & STATUSREG_RE) { + ca->slot_info[slot].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 == NULL) { + if (ca->slot_info[slot].rx_buffer.data == NULL) { + status = -EIO; + goto exit; + } + dvb_ringbuffer_pkt_write(&ca->slot_info[slot].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; +} + + +/** + * This function talks to an EN50221 CAM control interface. It writes a buffer of data + * to a CAM. + * + * @param ca CA instance. + * @param slot Slot to write to. + * @param ebuf The data in this buffer is treated as a complete link-level packet to + * be written. + * @param count Size of ebuf. + * + * @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 status; + int i; + + dprintk("%s\n", __func__); + + + /* sanity check */ + if (bytes_write > ca->slot_info[slot].link_buf_size) + return -EINVAL; + + /* 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. */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_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 */ + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, + IRQEN | CMDREG_HC)) != 0) + goto exit; + + /* check if interface is still free */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (!(status & STATUSREG_FR)) { + /* it wasn't free => try again later */ + status = -EAGAIN; + goto exit; + } + + /* send the amount of data */ + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_HIGH, bytes_write >> 8)) != 0) + goto exit; + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_LOW, + bytes_write & 0xff)) != 0) + goto exit; + + /* send the buffer */ + for (i = 0; i < bytes_write; i++) { + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_DATA, buf[i])) != 0) + goto exit; + } + + /* check for write error (WE should now be 0) */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (status & STATUSREG_WE) { + ca->slot_info[slot].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; +} +EXPORT_SYMBOL(dvb_ca_en50221_camchange_irq); + + + +/* ******************************************************************************** */ +/* EN50221 higher level functions */ + + +/** + * A CAM has been removed => shut it down. + * + * @param ca CA instance. + * @param 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; +} +EXPORT_SYMBOL(dvb_ca_en50221_camready_irq); + + +/** + * A CAMCHANGE IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + * @param 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; + + 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; + } + + ca->slot_info[slot].camchange_type = change_type; + atomic_inc(&ca->slot_info[slot].camchange_count); + dvb_ca_en50221_thread_wakeup(ca); +} +EXPORT_SYMBOL(dvb_ca_en50221_frda_irq); + + +/** + * A CAMREADY IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_camready_irq(struct dvb_ca_en50221 *pubca, int slot) +{ + struct dvb_ca_private *ca = pubca->private; + + dprintk("CAMREADY IRQ slot:%i\n", slot); + + if (ca->slot_info[slot].slot_state == DVB_CA_SLOTSTATE_WAITREADY) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_VALIDATE; + dvb_ca_en50221_thread_wakeup(ca); + } +} + + +/** + * An FR or DA IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_frda_irq(struct dvb_ca_en50221 *pubca, int slot) +{ + struct dvb_ca_private *ca = pubca->private; + int flags; + + dprintk("FR/DA IRQ slot:%i\n", slot); + + switch (ca->slot_info[slot].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"); + ca->slot_info[slot].da_irq_supported = 1; + } + break; + + case DVB_CA_SLOTSTATE_RUNNING: + if (ca->open) + dvb_ca_en50221_thread_wakeup(ca); + break; + } +} + + + +/* ******************************************************************************** */ +/* EN50221 thread functions */ + +/** + * Wake up the DVB CA thread + * + * @param 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); +} + +/** + * Update the delay used by the thread. + * + * @param 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++) { + switch (ca->slot_info[slot].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 ((!ca->slot_info[slot].da_irq_supported) || + (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_DA))) + delay = HZ / 10; /* 100ms */ + } + break; + } + + if (delay < curdelay) + curdelay = delay; + } + + ca->delay = curdelay; +} + + + +/** + * 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; + int flags; + int status; + int pktcount; + void *rxbuf; + + 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++) { + + mutex_lock(&ca->slot_info[slot].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 (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_NONE) + dvb_ca_en50221_slot_shutdown(ca, slot); + + /* if a CAM is NOW present, initialise it */ + if (ca->slot_info[slot].camchange_type == DVB_CA_EN50221_CAMCHANGE_INSERTED) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_UNINITIALISED; + } + + /* we've handled one CAMCHANGE */ + dvb_ca_en50221_thread_update_delay(ca); + atomic_dec(&ca->slot_info[slot].camchange_count); + } + + // CAM state machine + switch (ca->slot_info[slot].slot_state) { + case DVB_CA_SLOTSTATE_NONE: + case DVB_CA_SLOTSTATE_INVALID: + // no action needed + break; + + case DVB_CA_SLOTSTATE_UNINITIALISED: + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_WAITREADY; + ca->pub->slot_reset(ca->pub, slot); + ca->slot_info[slot].timeout = jiffies + (INIT_TIMEOUT_SECS * HZ); + break; + + case DVB_CA_SLOTSTATE_WAITREADY: + if (time_after(jiffies, ca->slot_info[slot].timeout)) { + printk("dvb_ca adaptor %d: PC card did not respond :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].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) { + /* 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); + break; + } + } + + printk("dvb_ca adapter %d: Invalid PC card inserted :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + if (dvb_ca_en50221_set_configoption(ca, slot) != 0) { + printk("dvb_ca adapter %d: Unable to initialise CAM :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].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) { + printk("dvb_ca adapter %d: Unable to reset CAM IF\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + dprintk("DVB CAM validated successfully\n"); + + ca->slot_info[slot].timeout = jiffies + (INIT_TIMEOUT_SECS * HZ); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_WAITFR; + ca->wakeup = 1; + break; + + case DVB_CA_SLOTSTATE_WAITFR: + if (time_after(jiffies, ca->slot_info[slot].timeout)) { + printk("dvb_ca adapter %d: DVB CAM did not respond :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].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) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + ca->wakeup = 1; + } + break; + + case DVB_CA_SLOTSTATE_LINKINIT: + if (dvb_ca_en50221_link_init(ca, slot) != 0) { + /* 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); + break; + } + } + + printk("dvb_ca adapter %d: DVB CAM link initialisation failed :(\n", ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + + if (ca->slot_info[slot].rx_buffer.data == NULL) { + rxbuf = vmalloc(RX_BUFFER_SIZE); + if (rxbuf == NULL) { + printk("dvb_ca adapter %d: Unable to allocate CAM rx buffer :(\n", ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + dvb_ringbuffer_init(&ca->slot_info[slot].rx_buffer, rxbuf, RX_BUFFER_SIZE); + } + + ca->pub->slot_ts_enable(ca->pub, slot); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_RUNNING; + dvb_ca_en50221_thread_update_delay(ca); + printk("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 ((status = 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 dont 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) { + // dont sleep; there is likely to be more data to read + ca->wakeup = 1; + break; + } + } + break; + } + + mutex_unlock(&ca->slot_info[slot].slot_lock); + } + } + + return 0; +} + + + +/* ******************************************************************************** */ +/* EN50221 IO interface functions */ + +/** + * Real ioctl implementation. + * NOTE: CA_SEND_MSG/CA_GET_MSG ioctls have userspace buffers passed to them. + * + * @param inode Inode concerned. + * @param file File concerned. + * @param cmd IOCTL command. + * @param arg Associated argument. + * + * @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__); + + switch (cmd) { + case CA_RESET: + for (slot = 0; slot < ca->slot_count; slot++) { + mutex_lock(&ca->slot_info[slot].slot_lock); + if (ca->slot_info[slot].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(&ca->slot_info[slot].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; + + if ((info->num > ca->slot_count) || (info->num < 0)) + return -EINVAL; + + info->type = CA_CI_LINK; + info->flags = 0; + if ((ca->slot_info[info->num].slot_state != DVB_CA_SLOTSTATE_NONE) + && (ca->slot_info[info->num].slot_state != DVB_CA_SLOTSTATE_INVALID)) { + info->flags = CA_CI_MODULE_PRESENT; + } + if (ca->slot_info[info->num].slot_state == DVB_CA_SLOTSTATE_RUNNING) { + info->flags |= CA_CI_MODULE_READY; + } + break; + } + + default: + err = -EINVAL; + break; + } + + return err; +} + + +/** + * Wrapper for ioctl implementation. + * + * @param inode Inode concerned. + * @param file File concerned. + * @param cmd IOCTL command. + * @param 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); +} + + +/** + * Implementation of write() syscall. + * + * @param file File structure. + * @param buf Source buffer. + * @param count Size of source buffer. + * @param 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; + 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; + + /* check if the slot is actually running */ + if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING) + return -EINVAL; + + /* fragment the packets & store in the buffer */ + while (fragpos < count) { + fraglen = ca->slot_info[slot].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 (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING) { + status = -EIO; + goto exit; + } + + mutex_lock(&ca->slot_info[slot].slot_lock); + status = dvb_ca_en50221_write_data(ca, slot, fragbuf, fraglen + 2); + mutex_unlock(&ca->slot_info[slot].slot_lock); + if (status == (fraglen + 2)) { + written = 1; + break; + } + if (status != -EAGAIN) + goto exit; + + msleep(1); + } + 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)) { + if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING) + goto nextslot; + + if (ca->slot_info[slot].rx_buffer.data == NULL) { + return 0; + } + + idx = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer, -1, &fraglen); + while (idx != -1) { + dvb_ringbuffer_pkt_read(&ca->slot_info[slot].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(&ca->slot_info[slot].rx_buffer, idx, &fraglen); + } + +nextslot: + slot = (slot + 1) % ca->slot_count; + slot_count++; + } + + ca->next_read_slot = slot; + return found; +} + + +/** + * Implementation of read() syscall. + * + * @param file File structure. + * @param buf Destination buffer. + * @param count Size of destination buffer. + * @param 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; + 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 */ + if ((status = dvb_ca_en50221_io_read_condition(ca, &result, &slot)) == 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; + } + + idx = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer, -1, &fraglen); + pktlen = 2; + do { + if (idx == -1) { + printk("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(&ca->slot_info[slot].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; + } + + if ((status = dvb_ringbuffer_pkt_read_user(&ca->slot_info[slot].rx_buffer, idx, 2, + buf + pktlen, fraglen)) < 0) { + goto exit; + } + pktlen += fraglen; + } + + if ((hdr[1] & 0x80) == 0) + last_fragment = 1; + dispose = 1; + } + + idx2 = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer, idx, &fraglen); + if (dispose) + dvb_ringbuffer_pkt_dispose(&ca->slot_info[slot].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; +} + + +/** + * Implementation of file open syscall. + * + * @param inode Inode concerned. + * @param 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__); + + if (!try_module_get(ca->pub->owner)) + return -EIO; + + err = dvb_generic_open(inode, file); + if (err < 0) { + module_put(ca->pub->owner); + return err; + } + + for (i = 0; i < ca->slot_count; i++) { + + if (ca->slot_info[i].slot_state == DVB_CA_SLOTSTATE_RUNNING) { + if (ca->slot_info[i].rx_buffer.data != NULL) { + /* it is safe to call this here without locks because + * ca->open == 0. Data is not read in this case */ + dvb_ringbuffer_flush(&ca->slot_info[i].rx_buffer); + } + } + } + + ca->open = 1; + dvb_ca_en50221_thread_update_delay(ca); + dvb_ca_en50221_thread_wakeup(ca); + + return 0; +} + + +/** + * Implementation of file close syscall. + * + * @param inode Inode concerned. + * @param 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__); + + /* 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); + + return err; +} + + +/** + * Implementation of poll() syscall. + * + * @param file File concerned. + * @param wait poll wait table. + * + * @return Standard poll mask. + */ +static unsigned int 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; + unsigned int mask = 0; + int slot; + int result = 0; + + dprintk("%s\n", __func__); + + if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1) { + mask |= POLLIN; + } + + /* if there is something, return now */ + if (mask) + return mask; + + /* wait for something to happen */ + poll_wait(file, &ca->wait_queue, wait); + + if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1) { + mask |= POLLIN; + } + + return mask; +} +EXPORT_SYMBOL(dvb_ca_en50221_init); + + +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 struct dvb_device dvbdev_ca = { + .priv = NULL, + .users = 1, + .readers = 1, + .writers = 1, + .fops = &dvb_ca_fops, +}; + + +/* ******************************************************************************** */ +/* Initialisation/shutdown functions */ + + +/** + * Initialise a new DVB CA EN50221 interface device. + * + * @param dvb_adapter DVB adapter to attach the new CA device to. + * @param ca The dvb_ca instance. + * @param flags Flags describing the CA device (DVB_CA_FLAG_*). + * @param 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 */ + if ((ca = kzalloc(sizeof(struct dvb_ca_private), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto error; + } + ca->pub = pubca; + ca->flags = flags; + ca->slot_count = slot_count; + if ((ca->slot_info = kcalloc(slot_count, sizeof(struct dvb_ca_slot), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto error; + } + 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); + if (ret) + goto error; + + /* now initialise each slot */ + for (i = 0; i < slot_count; i++) { + memset(&ca->slot_info[i], 0, sizeof(struct dvb_ca_slot)); + ca->slot_info[i].slot_state = DVB_CA_SLOTSTATE_NONE; + atomic_set(&ca->slot_info[i].camchange_count, 0); + ca->slot_info[i].camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED; + mutex_init(&ca->slot_info[i].slot_lock); + } + + if (signal_pending(current)) { + ret = -EINTR; + goto error; + } + 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); + printk("dvb_ca_init: failed to start kernel_thread (%d)\n", + ret); + goto error; + } + return 0; + +error: + if (ca != NULL) { + if (ca->dvbdev != NULL) + dvb_unregister_device(ca->dvbdev); + kfree(ca->slot_info); + kfree(ca); + } + pubca->private = NULL; + return ret; +} +EXPORT_SYMBOL(dvb_ca_en50221_release); + + + +/** + * Release a DVB CA EN50221 interface device. + * + * @param ca_dev The dvb_device_t instance for the CA device. + * @param ca 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__); + + /* 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); + vfree(ca->slot_info[i].rx_buffer.data); + } + kfree(ca->slot_info); + dvb_unregister_device(ca->dvbdev); + kfree(ca); + pubca->private = NULL; +} diff --git a/drivers/media/dvb-core/dvb_ca_en50221.h b/drivers/media/dvb-core/dvb_ca_en50221.h new file mode 100644 index 000000000000..7df2e141187a --- /dev/null +++ b/drivers/media/dvb-core/dvb_ca_en50221.h @@ -0,0 +1,136 @@ +/* + * dvb_ca.h: generic DVB functions for EN50221 CA interfaces + * + * Copyright (C) 2004 Andrew de Quincey + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DVB_CA_EN50221_H_ +#define _DVB_CA_EN50221_H_ + +#include <linux/list.h> +#include <linux/dvb/ca.h> + +#include "dvbdev.h" + +#define DVB_CA_EN50221_POLL_CAM_PRESENT 1 +#define DVB_CA_EN50221_POLL_CAM_CHANGED 2 +#define DVB_CA_EN50221_POLL_CAM_READY 4 + +#define DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE 1 +#define DVB_CA_EN50221_FLAG_IRQ_FR 2 +#define DVB_CA_EN50221_FLAG_IRQ_DA 4 + +#define DVB_CA_EN50221_CAMCHANGE_REMOVED 0 +#define DVB_CA_EN50221_CAMCHANGE_INSERTED 1 + + + +/* Structure describing a CA interface */ +struct dvb_ca_en50221 { + + /* the module owning this structure */ + struct module* owner; + + /* NOTE: the read_*, write_* and poll_slot_status functions will be + * called for different slots concurrently and need to use locks where + * and if appropriate. There will be no concurrent access to one slot. + */ + + /* functions for accessing attribute memory on the CAM */ + int (*read_attribute_mem)(struct dvb_ca_en50221* ca, int slot, int address); + int (*write_attribute_mem)(struct dvb_ca_en50221* ca, int slot, int address, u8 value); + + /* functions for accessing the control interface on the CAM */ + int (*read_cam_control)(struct dvb_ca_en50221* ca, int slot, u8 address); + int (*write_cam_control)(struct dvb_ca_en50221* ca, int slot, u8 address, u8 value); + + /* Functions for controlling slots */ + int (*slot_reset)(struct dvb_ca_en50221* ca, int slot); + int (*slot_shutdown)(struct dvb_ca_en50221* ca, int slot); + int (*slot_ts_enable)(struct dvb_ca_en50221* ca, int slot); + + /* + * Poll slot status. + * Only necessary if DVB_CA_FLAG_EN50221_IRQ_CAMCHANGE is not set + */ + int (*poll_slot_status)(struct dvb_ca_en50221* ca, int slot, int open); + + /* private data, used by caller */ + void* data; + + /* Opaque data used by the dvb_ca core. Do not modify! */ + void* private; +}; + + + + +/* ******************************************************************************** */ +/* Functions for reporting IRQ events */ + +/** + * A CAMCHANGE IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + * @param 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); + +/** + * A CAMREADY IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_camready_irq(struct dvb_ca_en50221* pubca, int slot); + +/** + * An FR or a DA IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_frda_irq(struct dvb_ca_en50221* ca, int slot); + + + +/* ******************************************************************************** */ +/* Initialisation/shutdown functions */ + +/** + * Initialise a new DVB CA device. + * + * @param dvb_adapter DVB adapter to attach the new CA device to. + * @param ca The dvb_ca instance. + * @param flags Flags describing the CA device (DVB_CA_EN50221_FLAG_*). + * @param slot_count Number of slots supported. + * + * @return 0 on success, nonzero on failure + */ +extern int dvb_ca_en50221_init(struct dvb_adapter *dvb_adapter, struct dvb_ca_en50221* ca, int flags, int slot_count); + +/** + * Release a DVB CA device. + * + * @param ca The associated dvb_ca instance. + */ +extern void dvb_ca_en50221_release(struct dvb_ca_en50221* ca); + + + +#endif diff --git a/drivers/media/dvb-core/dvb_demux.c b/drivers/media/dvb-core/dvb_demux.c new file mode 100644 index 000000000000..17cb81fd194c --- /dev/null +++ b/drivers/media/dvb-core/dvb_demux.c @@ -0,0 +1,1318 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/string.h> +#include <linux/crc32.h> +#include <asm/uaccess.h> +#include <asm/div64.h> + +#include "dvb_demux.h" + +#define NOBUFS +/* +** #define DVB_DEMUX_SECTION_LOSS_LOG to monitor payload loss in the syslog +*/ +// #define DVB_DEMUX_SECTION_LOSS_LOG + +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_tscheck(x...) do { \ + if (dvb_demux_tscheck && printk_ratelimit()) \ + printk(x); \ + } 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; + feed->cc = cc; + if (!ccok) + printk("missed packet!\n"); + */ + + if (buf[1] & 0x40) // PUSI ? + feed->peslen = 0xfffa; + + feed->peslen += count; + + return feed->cb.ts(&buf[p], count, NULL, 0, &feed->feed.ts, DMX_OK); +} + +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, DMX_OK); +} + +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)) + 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; + +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + if (sec->secbufp < sec->tsfeedp) { + int i, 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) { + printk("dvb_demux.c section ts padding loss: %d/%d\n", + n, sec->tsfeedp); + printk("dvb_demux.c pad data:"); + for (i = 0; i < n; i++) + printk(" %02x", sec->secbuf[i]); + printk("\n"); + } + } +#endif + + 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 = 0 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 = 1 + */ +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) { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printk("dvb_demux.c section buffer full loss: %d/%d\n", + sec->tsfeedp + len - DMX_MAX_SECFEED_SIZE, + DMX_MAX_SECFEED_SIZE); +#endif + 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); +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else + printk("dvb_demux.c pusi not seen, discarding section data\n"); +#endif + 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; + feed->cc = 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) { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printk("dvb_demux.c discontinuity detected %d bytes lost\n", + count); + /* + * those bytes under sume circumstances will again be reported + * in the following dvb_dmx_swfilter_section_new + */ +#endif + /* + * Discontinuity detected. Reset pusi_seen = 0 to + * stop feeding of suspicious data until next PUSI=1 arrives + */ + feed->pusi_seen = 0; + dvb_dmx_swfilter_section_new(feed); + } + + 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 = 1 */ + feed->pusi_seen = 1; + dvb_dmx_swfilter_section_new(feed); + dvb_dmx_swfilter_section_copy_dump(feed, after, + after_len); + } +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else if (count > 0) + printk("dvb_demux.c PUSI=1 but %d bytes lost\n", count); +#endif + } 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, + DMX_OK); + } + 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) { + struct timespec cur_time, delta_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 = current_kernel_time(); + + if (demux->speed_last_time.tv_sec != 0 && + demux->speed_last_time.tv_nsec != 0) { + delta_time = timespec_sub(cur_time, + demux->speed_last_time); + speed_bytes = (u64)demux->speed_pkts_cnt + * 188 * 8; + /* convert to 1024 basis */ + speed_bytes = 1000 * div64_u64(speed_bytes, + 1024); + speed_timedelta = + (u64)timespec_to_ns(&delta_time); + speed_timedelta = div64_u64(speed_timedelta, + 1000000); /* nsec -> usec */ + printk(KERN_INFO "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) { + dprintk_tscheck("TEI detected. " + "PID=0x%x data1=0x%x\n", + pid, buf[1]); + /* data in this packet cant 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] & 0xf) != demux->cnt_storage[pid]) + 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) + 1)&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, DMX_OK); + } +} + +void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, + size_t count) +{ + spin_lock(&demux->lock); + + while (count--) { + if (buf[0] == 0x47) + dvb_dmx_swfilter_packet(demux, buf); + buf += 188; + } + + spin_unlock(&demux->lock); +} + +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; + + spin_lock(&demux->lock); + + 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(&demux->lock); +} + +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) +{ + spin_lock(&demux->lock); + + demux->feed->cb.ts(buf, count, NULL, 0, &demux->feed->feed.ts, DMX_OK); + + spin_unlock(&demux->lock); +} +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)) { + printk(KERN_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))) { + printk(KERN_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, + size_t circular_buffer_size, struct timespec 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_TS_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->buffer_size = circular_buffer_size; + feed->timeout = timeout; + feed->ts_type = ts_type; + feed->pes_type = pes_type; + + if (feed->buffer_size) { +#ifdef NOBUFS + feed->buffer = NULL; +#else + feed->buffer = vmalloc(feed->buffer_size); + if (!feed->buffer) { + mutex_unlock(&demux->mutex); + return -ENOMEM; + } +#endif + } + + 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 = NULL; + + (*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; + } +#ifndef NOBUFS + vfree(feed->buffer); + feed->buffer = NULL; +#endif + + 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_TS_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, size_t circular_buffer_size, + 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->buffer_size = circular_buffer_size; + dvbdmxfeed->feed.sec.check_crc = check_crc; + +#ifdef NOBUFS + dvbdmxfeed->buffer = NULL; +#else + dvbdmxfeed->buffer = vmalloc(dvbdmxfeed->buffer_size); + if (!dvbdmxfeed->buffer) { + mutex_unlock(&dvbdmx->mutex); + return -ENOMEM; + } +#endif + + 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 = 0; + 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 ? 1 : 0; + } 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; + + 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) + feed->stop_filtering(feed); + + 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->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; + dvbdmxfeed->buffer = 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; + } +#ifndef NOBUFS + vfree(dvbdmxfeed->buffer); + dvbdmxfeed->buffer = NULL; +#endif + 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(dvbdemux->filternum * sizeof(struct dvb_demux_filter)); + + if (!dvbdemux->filter) + return -ENOMEM; + + dvbdemux->feed = vmalloc(dvbdemux->feednum * sizeof(struct dvb_demux_feed)); + 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) + printk(KERN_WARNING "Couldn't allocate memory for TS/TEI check. Disabling it\n"); + + INIT_LIST_HEAD(&dvbdemux->frontend_list); + + for (i = 0; i < DMX_TS_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_demux.h b/drivers/media/dvb-core/dvb_demux.h new file mode 100644 index 000000000000..fa7188a253aa --- /dev/null +++ b/drivers/media/dvb-core/dvb_demux.h @@ -0,0 +1,151 @@ +/* + * dvb_demux.h: DVB kernel demux API + * + * Copyright (C) 2000-2001 Marcus Metzler & Ralph Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DVB_DEMUX_H_ +#define _DVB_DEMUX_H_ + +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> + +#include "demux.h" + +#define DMX_TYPE_TS 0 +#define DMX_TYPE_SEC 1 +#define DMX_TYPE_PES 2 + +#define DMX_STATE_FREE 0 +#define DMX_STATE_ALLOCATED 1 +#define DMX_STATE_SET 2 +#define DMX_STATE_READY 3 +#define DMX_STATE_GO 4 + +#define DVB_DEMUX_MASK_MAX 18 + +#define MAX_PID 0x1fff + +#define SPEED_PKTS_INTERVAL 50000 + +struct dvb_demux_filter { + struct dmx_section_filter filter; + u8 maskandmode[DMX_MAX_FILTER_SIZE]; + u8 maskandnotmode[DMX_MAX_FILTER_SIZE]; + int doneq; + + struct dvb_demux_filter *next; + struct dvb_demux_feed *feed; + int index; + int state; + int type; + + u16 hw_handle; + struct timer_list timer; +}; + +#define DMX_FEED_ENTRY(pos) list_entry(pos, struct dvb_demux_feed, list_head) + +struct dvb_demux_feed { + union { + struct dmx_ts_feed ts; + struct dmx_section_feed sec; + } feed; + + union { + dmx_ts_cb ts; + dmx_section_cb sec; + } cb; + + struct dvb_demux *demux; + void *priv; + int type; + int state; + u16 pid; + u8 *buffer; + int buffer_size; + + struct timespec timeout; + struct dvb_demux_filter *filter; + + int ts_type; + enum dmx_ts_pes pes_type; + + int cc; + int pusi_seen; /* prevents feeding of garbage from previous section */ + + u16 peslen; + + struct list_head list_head; + unsigned int index; /* a unique index for each feed (can be used as hardware pid filter index) */ +}; + +struct dvb_demux { + struct dmx_demux dmx; + void *priv; + int filternum; + int feednum; + int (*start_feed)(struct dvb_demux_feed *feed); + int (*stop_feed)(struct dvb_demux_feed *feed); + int (*write_to_decoder)(struct dvb_demux_feed *feed, + const u8 *buf, size_t len); + u32 (*check_crc32)(struct dvb_demux_feed *feed, + const u8 *buf, size_t len); + void (*memcopy)(struct dvb_demux_feed *feed, u8 *dst, + const u8 *src, size_t len); + + int users; +#define MAX_DVB_DEMUX_USERS 10 + struct dvb_demux_filter *filter; + struct dvb_demux_feed *feed; + + struct list_head frontend_list; + + struct dvb_demux_feed *pesfilter[DMX_TS_PES_OTHER]; + u16 pids[DMX_TS_PES_OTHER]; + int playing; + int recording; + +#define DMX_MAX_PID 0x2000 + struct list_head feed_list; + u8 tsbuf[204]; + int tsbufp; + + struct mutex mutex; + spinlock_t lock; + + uint8_t *cnt_storage; /* for TS continuity check */ + + struct timespec speed_last_time; /* for TS speed check */ + uint32_t speed_pkts_cnt; /* for TS speed check */ +}; + +int dvb_dmx_init(struct dvb_demux *dvbdemux); +void dvb_dmx_release(struct dvb_demux *dvbdemux); +void dvb_dmx_swfilter_packets(struct dvb_demux *dvbdmx, const u8 *buf, + size_t count); +void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count); +void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, + size_t count); +void dvb_dmx_swfilter_raw(struct dvb_demux *demux, const u8 *buf, + size_t count); + +#endif /* _DVB_DEMUX_H_ */ diff --git a/drivers/media/dvb-core/dvb_filter.c b/drivers/media/dvb-core/dvb_filter.c new file mode 100644 index 000000000000..772003fb1821 --- /dev/null +++ b/drivers/media/dvb-core/dvb_filter.c @@ -0,0 +1,603 @@ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include "dvb_filter.h" + +#if 0 +static unsigned int bitrates[3][16] = +{{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, + {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0}, + {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}}; +#endif + +static u32 freq[4] = {480, 441, 320, 0}; + +static unsigned int ac3_bitrates[32] = + {32,40,48,56,64,80,96,112,128,160,192,224,256,320,384,448,512,576,640, + 0,0,0,0,0,0,0,0,0,0,0,0,0}; + +static u32 ac3_frames[3][32] = + {{64,80,96,112,128,160,192,224,256,320,384,448,512,640,768,896,1024, + 1152,1280,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {69,87,104,121,139,174,208,243,278,348,417,487,557,696,835,975,1114, + 1253,1393,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {96,120,144,168,192,240,288,336,384,480,576,672,768,960,1152,1344, + 1536,1728,1920,0,0,0,0,0,0,0,0,0,0,0,0,0}}; + + + +#if 0 +static void setup_ts2pes(ipack *pa, ipack *pv, u16 *pida, u16 *pidv, + void (*pes_write)(u8 *buf, int count, void *data), + void *priv) +{ + dvb_filter_ipack_init(pa, IPACKS, pes_write); + dvb_filter_ipack_init(pv, IPACKS, pes_write); + pa->pid = pida; + pv->pid = pidv; + pa->data = priv; + pv->data = priv; +} +#endif + +#if 0 +static void ts_to_pes(ipack *p, u8 *buf) // don't need count (=188) +{ + u8 off = 0; + + if (!buf || !p ){ + printk("NULL POINTER IDIOT\n"); + return; + } + if (buf[1]&PAY_START) { + if (p->plength == MMAX_PLENGTH-6 && p->found>6){ + p->plength = p->found-6; + p->found = 0; + send_ipack(p); + dvb_filter_ipack_reset(p); + } + } + if (buf[3] & ADAPT_FIELD) { // adaptation field? + off = buf[4] + 1; + if (off+4 > 187) return; + } + dvb_filter_instant_repack(buf+4+off, TS_SIZE-4-off, p); +} +#endif + +#if 0 +/* needs 5 byte input, returns picture coding type*/ +static int read_picture_header(u8 *headr, struct mpg_picture *pic, int field, int pr) +{ + u8 pct; + + if (pr) printk( "Pic header: "); + pic->temporal_reference[field] = (( headr[0] << 2 ) | + (headr[1] & 0x03) )& 0x03ff; + if (pr) printk( " temp ref: 0x%04x", pic->temporal_reference[field]); + + pct = ( headr[1] >> 2 ) & 0x07; + pic->picture_coding_type[field] = pct; + if (pr) { + switch(pct){ + case I_FRAME: + printk( " I-FRAME"); + break; + case B_FRAME: + printk( " B-FRAME"); + break; + case P_FRAME: + printk( " P-FRAME"); + break; + } + } + + + pic->vinfo.vbv_delay = (( headr[1] >> 5 ) | ( headr[2] << 3) | + ( (headr[3] & 0x1F) << 11) ) & 0xffff; + + if (pr) printk( " vbv delay: 0x%04x", pic->vinfo.vbv_delay); + + pic->picture_header_parameter = ( headr[3] & 0xe0 ) | + ((headr[4] & 0x80) >> 3); + + if ( pct == B_FRAME ){ + pic->picture_header_parameter |= ( headr[4] >> 3 ) & 0x0f; + } + if (pr) printk( " pic head param: 0x%x", + pic->picture_header_parameter); + + return pct; +} +#endif + +#if 0 +/* needs 4 byte input */ +static int read_gop_header(u8 *headr, struct mpg_picture *pic, int pr) +{ + if (pr) printk("GOP header: "); + + pic->time_code = (( headr[0] << 17 ) | ( headr[1] << 9) | + ( headr[2] << 1 ) | (headr[3] &0x01)) & 0x1ffffff; + + if (pr) printk(" time: %d:%d.%d ", (headr[0]>>2)& 0x1F, + ((headr[0]<<4)& 0x30)| ((headr[1]>>4)& 0x0F), + ((headr[1]<<3)& 0x38)| ((headr[2]>>5)& 0x0F)); + + if ( ( headr[3] & 0x40 ) != 0 ){ + pic->closed_gop = 1; + } else { + pic->closed_gop = 0; + } + if (pr) printk("closed: %d", pic->closed_gop); + + if ( ( headr[3] & 0x20 ) != 0 ){ + pic->broken_link = 1; + } else { + pic->broken_link = 0; + } + if (pr) printk(" broken: %d\n", pic->broken_link); + + return 0; +} +#endif + +#if 0 +/* needs 8 byte input */ +static int read_sequence_header(u8 *headr, struct dvb_video_info *vi, int pr) +{ + int sw; + int form = -1; + + if (pr) printk("Reading sequence header\n"); + + vi->horizontal_size = ((headr[1] &0xF0) >> 4) | (headr[0] << 4); + vi->vertical_size = ((headr[1] &0x0F) << 8) | (headr[2]); + + sw = (int)((headr[3]&0xF0) >> 4) ; + + switch( sw ){ + case 1: + if (pr) + printk("Videostream: ASPECT: 1:1"); + vi->aspect_ratio = 100; + break; + case 2: + if (pr) + printk("Videostream: ASPECT: 4:3"); + vi->aspect_ratio = 133; + break; + case 3: + if (pr) + printk("Videostream: ASPECT: 16:9"); + vi->aspect_ratio = 177; + break; + case 4: + if (pr) + printk("Videostream: ASPECT: 2.21:1"); + vi->aspect_ratio = 221; + break; + + case 5 ... 15: + if (pr) + printk("Videostream: ASPECT: reserved"); + vi->aspect_ratio = 0; + break; + + default: + vi->aspect_ratio = 0; + return -1; + } + + if (pr) + printk(" Size = %dx%d",vi->horizontal_size,vi->vertical_size); + + sw = (int)(headr[3]&0x0F); + + switch ( sw ) { + case 1: + if (pr) + printk(" FRate: 23.976 fps"); + vi->framerate = 23976; + form = -1; + break; + case 2: + if (pr) + printk(" FRate: 24 fps"); + vi->framerate = 24000; + form = -1; + break; + case 3: + if (pr) + printk(" FRate: 25 fps"); + vi->framerate = 25000; + form = VIDEO_MODE_PAL; + break; + case 4: + if (pr) + printk(" FRate: 29.97 fps"); + vi->framerate = 29970; + form = VIDEO_MODE_NTSC; + break; + case 5: + if (pr) + printk(" FRate: 30 fps"); + vi->framerate = 30000; + form = VIDEO_MODE_NTSC; + break; + case 6: + if (pr) + printk(" FRate: 50 fps"); + vi->framerate = 50000; + form = VIDEO_MODE_PAL; + break; + case 7: + if (pr) + printk(" FRate: 60 fps"); + vi->framerate = 60000; + form = VIDEO_MODE_NTSC; + break; + } + + vi->bit_rate = (headr[4] << 10) | (headr[5] << 2) | (headr[6] & 0x03); + + vi->vbv_buffer_size + = (( headr[6] & 0xF8) >> 3 ) | (( headr[7] & 0x1F )<< 5); + + if (pr){ + printk(" BRate: %d Mbit/s",4*(vi->bit_rate)/10000); + printk(" vbvbuffer %d",16*1024*(vi->vbv_buffer_size)); + printk("\n"); + } + + vi->video_format = form; + + return 0; +} +#endif + + +#if 0 +static int get_vinfo(u8 *mbuf, int count, struct dvb_video_info *vi, int pr) +{ + u8 *headr; + int found = 0; + int c = 0; + + while (found < 4 && c+4 < count){ + u8 *b; + + b = mbuf+c; + if ( b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x01 + && b[3] == 0xb3) found = 4; + else { + c++; + } + } + + if (! found) return -1; + c += 4; + if (c+12 >= count) return -1; + headr = mbuf+c; + if (read_sequence_header(headr, vi, pr) < 0) return -1; + vi->off = c-4; + return 0; +} +#endif + + +#if 0 +static int get_ainfo(u8 *mbuf, int count, struct dvb_audio_info *ai, int pr) +{ + u8 *headr; + int found = 0; + int c = 0; + int fr = 0; + + while (found < 2 && c < count){ + u8 b[2]; + memcpy( b, mbuf+c, 2); + + if ( b[0] == 0xff && (b[1] & 0xf8) == 0xf8) + found = 2; + else { + c++; + } + } + + if (!found) return -1; + + if (c+3 >= count) return -1; + headr = mbuf+c; + + ai->layer = (headr[1] & 0x06) >> 1; + + if (pr) + printk("Audiostream: Layer: %d", 4-ai->layer); + + + ai->bit_rate = bitrates[(3-ai->layer)][(headr[2] >> 4 )]*1000; + + if (pr){ + if (ai->bit_rate == 0) + printk(" Bit rate: free"); + else if (ai->bit_rate == 0xf) + printk(" BRate: reserved"); + else + printk(" BRate: %d kb/s", ai->bit_rate/1000); + } + + fr = (headr[2] & 0x0c ) >> 2; + ai->frequency = freq[fr]*100; + if (pr){ + if (ai->frequency == 3) + printk(" Freq: reserved\n"); + else + printk(" Freq: %d kHz\n",ai->frequency); + + } + ai->off = c; + return 0; +} +#endif + + +int dvb_filter_get_ac3info(u8 *mbuf, int count, struct dvb_audio_info *ai, int pr) +{ + u8 *headr; + int found = 0; + int c = 0; + u8 frame = 0; + int fr = 0; + + while ( !found && c < count){ + u8 *b = mbuf+c; + + if ( b[0] == 0x0b && b[1] == 0x77 ) + found = 1; + else { + c++; + } + } + + if (!found) return -1; + if (pr) + printk("Audiostream: AC3"); + + ai->off = c; + if (c+5 >= count) return -1; + + ai->layer = 0; // 0 for AC3 + headr = mbuf+c+2; + + frame = (headr[2]&0x3f); + ai->bit_rate = ac3_bitrates[frame >> 1]*1000; + + if (pr) + printk(" BRate: %d kb/s", (int) ai->bit_rate/1000); + + ai->frequency = (headr[2] & 0xc0 ) >> 6; + fr = (headr[2] & 0xc0 ) >> 6; + ai->frequency = freq[fr]*100; + if (pr) printk (" Freq: %d Hz\n", (int) ai->frequency); + + + ai->framesize = ac3_frames[fr][frame >> 1]; + if ((frame & 1) && (fr == 1)) ai->framesize++; + ai->framesize = ai->framesize << 1; + if (pr) printk (" Framesize %d\n",(int) ai->framesize); + + + return 0; +} +EXPORT_SYMBOL(dvb_filter_get_ac3info); + + +#if 0 +static u8 *skip_pes_header(u8 **bufp) +{ + u8 *inbuf = *bufp; + u8 *buf = inbuf; + u8 *pts = NULL; + int skip = 0; + + static const int mpeg1_skip_table[16] = { + 1, 0xffff, 5, 10, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff + }; + + + if ((inbuf[6] & 0xc0) == 0x80){ /* mpeg2 */ + if (buf[7] & PTS_ONLY) + pts = buf+9; + else pts = NULL; + buf = inbuf + 9 + inbuf[8]; + } else { /* mpeg1 */ + for (buf = inbuf + 6; *buf == 0xff; buf++) + if (buf == inbuf + 6 + 16) { + break; + } + if ((*buf & 0xc0) == 0x40) + buf += 2; + skip = mpeg1_skip_table [*buf >> 4]; + if (skip == 5 || skip == 10) pts = buf; + else pts = NULL; + + buf += mpeg1_skip_table [*buf >> 4]; + } + + *bufp = buf; + return pts; +} +#endif + +#if 0 +static void initialize_quant_matrix( u32 *matrix ) +{ + int i; + + matrix[0] = 0x08101013; + matrix[1] = 0x10131616; + matrix[2] = 0x16161616; + matrix[3] = 0x1a181a1b; + matrix[4] = 0x1b1b1a1a; + matrix[5] = 0x1a1a1b1b; + matrix[6] = 0x1b1d1d1d; + matrix[7] = 0x2222221d; + matrix[8] = 0x1d1d1b1b; + matrix[9] = 0x1d1d2020; + matrix[10] = 0x22222526; + matrix[11] = 0x25232322; + matrix[12] = 0x23262628; + matrix[13] = 0x28283030; + matrix[14] = 0x2e2e3838; + matrix[15] = 0x3a454553; + + for ( i = 16 ; i < 32 ; i++ ) + matrix[i] = 0x10101010; +} +#endif + +#if 0 +static void initialize_mpg_picture(struct mpg_picture *pic) +{ + int i; + + /* set MPEG1 */ + pic->mpeg1_flag = 1; + pic->profile_and_level = 0x4A ; /* MP@LL */ + pic->progressive_sequence = 1; + pic->low_delay = 0; + + pic->sequence_display_extension_flag = 0; + for ( i = 0 ; i < 4 ; i++ ){ + pic->frame_centre_horizontal_offset[i] = 0; + pic->frame_centre_vertical_offset[i] = 0; + } + pic->last_frame_centre_horizontal_offset = 0; + pic->last_frame_centre_vertical_offset = 0; + + pic->picture_display_extension_flag[0] = 0; + pic->picture_display_extension_flag[1] = 0; + pic->sequence_header_flag = 0; + pic->gop_flag = 0; + pic->sequence_end_flag = 0; +} +#endif + +#if 0 +static void mpg_set_picture_parameter( int32_t field_type, struct mpg_picture *pic ) +{ + int16_t last_h_offset; + int16_t last_v_offset; + + int16_t *p_h_offset; + int16_t *p_v_offset; + + if ( pic->mpeg1_flag ){ + pic->picture_structure[field_type] = VIDEO_FRAME_PICTURE; + pic->top_field_first = 0; + pic->repeat_first_field = 0; + pic->progressive_frame = 1; + pic->picture_coding_parameter = 0x000010; + } + + /* Reset flag */ + pic->picture_display_extension_flag[field_type] = 0; + + last_h_offset = pic->last_frame_centre_horizontal_offset; + last_v_offset = pic->last_frame_centre_vertical_offset; + if ( field_type == FIRST_FIELD ){ + p_h_offset = pic->frame_centre_horizontal_offset; + p_v_offset = pic->frame_centre_vertical_offset; + *p_h_offset = last_h_offset; + *(p_h_offset + 1) = last_h_offset; + *(p_h_offset + 2) = last_h_offset; + *p_v_offset = last_v_offset; + *(p_v_offset + 1) = last_v_offset; + *(p_v_offset + 2) = last_v_offset; + } else { + pic->frame_centre_horizontal_offset[3] = last_h_offset; + pic->frame_centre_vertical_offset[3] = last_v_offset; + } +} +#endif + +#if 0 +static void init_mpg_picture( struct mpg_picture *pic, int chan, int32_t field_type) +{ + pic->picture_header = 0; + pic->sequence_header_data + = ( INIT_HORIZONTAL_SIZE << 20 ) + | ( INIT_VERTICAL_SIZE << 8 ) + | ( INIT_ASPECT_RATIO << 4 ) + | ( INIT_FRAME_RATE ); + pic->mpeg1_flag = 0; + pic->vinfo.horizontal_size + = INIT_DISP_HORIZONTAL_SIZE; + pic->vinfo.vertical_size + = INIT_DISP_VERTICAL_SIZE; + pic->picture_display_extension_flag[field_type] + = 0; + pic->pts_flag[field_type] = 0; + + pic->sequence_gop_header = 0; + pic->picture_header = 0; + pic->sequence_header_flag = 0; + pic->gop_flag = 0; + pic->sequence_end_flag = 0; + pic->sequence_display_extension_flag = 0; + pic->last_frame_centre_horizontal_offset = 0; + pic->last_frame_centre_vertical_offset = 0; + pic->channel = chan; +} +#endif + +void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid, + dvb_filter_pes2ts_cb_t *cb, void *priv) +{ + unsigned char *buf=p2ts->buf; + + buf[0]=0x47; + buf[1]=(pid>>8); + buf[2]=pid&0xff; + p2ts->cc=0; + p2ts->cb=cb; + p2ts->priv=priv; +} +EXPORT_SYMBOL(dvb_filter_pes2ts_init); + +int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes, + int len, int payload_start) +{ + unsigned char *buf=p2ts->buf; + int ret=0, rest; + + //len=6+((pes[4]<<8)|pes[5]); + + if (payload_start) + buf[1]|=0x40; + else + buf[1]&=~0x40; + while (len>=184) { + buf[3]=0x10|((p2ts->cc++)&0x0f); + memcpy(buf+4, pes, 184); + if ((ret=p2ts->cb(p2ts->priv, buf))) + return ret; + len-=184; pes+=184; + buf[1]&=~0x40; + } + if (!len) + return 0; + buf[3]=0x30|((p2ts->cc++)&0x0f); + rest=183-len; + if (rest) { + buf[5]=0x00; + if (rest-1) + memset(buf+6, 0xff, rest-1); + } + buf[4]=rest; + memcpy(buf+5+rest, pes, len); + return p2ts->cb(p2ts->priv, buf); +} +EXPORT_SYMBOL(dvb_filter_pes2ts); diff --git a/drivers/media/dvb-core/dvb_filter.h b/drivers/media/dvb-core/dvb_filter.h new file mode 100644 index 000000000000..375e3be184b1 --- /dev/null +++ b/drivers/media/dvb-core/dvb_filter.h @@ -0,0 +1,246 @@ +/* + * dvb_filter.h + * + * Copyright (C) 2003 Convergence GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DVB_FILTER_H_ +#define _DVB_FILTER_H_ + +#include <linux/slab.h> + +#include "demux.h" + +typedef int (dvb_filter_pes2ts_cb_t) (void *, unsigned char *); + +struct dvb_filter_pes2ts { + unsigned char buf[188]; + unsigned char cc; + dvb_filter_pes2ts_cb_t *cb; + void *priv; +}; + +void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid, + dvb_filter_pes2ts_cb_t *cb, void *priv); + +int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes, + int len, int payload_start); + + +#define PROG_STREAM_MAP 0xBC +#define PRIVATE_STREAM1 0xBD +#define PADDING_STREAM 0xBE +#define PRIVATE_STREAM2 0xBF +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +#define DVB_PICTURE_START 0x00 +#define DVB_USER_START 0xb2 +#define DVB_SEQUENCE_HEADER 0xb3 +#define DVB_SEQUENCE_ERROR 0xb4 +#define DVB_EXTENSION_START 0xb5 +#define DVB_SEQUENCE_END 0xb7 +#define DVB_GOP_START 0xb8 +#define DVB_EXCEPT_SLICE 0xb0 + +#define SEQUENCE_EXTENSION 0x01 +#define SEQUENCE_DISPLAY_EXTENSION 0x02 +#define PICTURE_CODING_EXTENSION 0x08 +#define QUANT_MATRIX_EXTENSION 0x03 +#define PICTURE_DISPLAY_EXTENSION 0x07 + +#define I_FRAME 0x01 +#define B_FRAME 0x02 +#define P_FRAME 0x03 + +/* Initialize sequence_data */ +#define INIT_HORIZONTAL_SIZE 720 +#define INIT_VERTICAL_SIZE 576 +#define INIT_ASPECT_RATIO 0x02 +#define INIT_FRAME_RATE 0x03 +#define INIT_DISP_HORIZONTAL_SIZE 540 +#define INIT_DISP_VERTICAL_SIZE 576 + + +//flags2 +#define PTS_DTS_FLAGS 0xC0 +#define ESCR_FLAG 0x20 +#define ES_RATE_FLAG 0x10 +#define DSM_TRICK_FLAG 0x08 +#define ADD_CPY_FLAG 0x04 +#define PES_CRC_FLAG 0x02 +#define PES_EXT_FLAG 0x01 + +//pts_dts flags +#define PTS_ONLY 0x80 +#define PTS_DTS 0xC0 + +#define TS_SIZE 188 +#define TRANS_ERROR 0x80 +#define PAY_START 0x40 +#define TRANS_PRIO 0x20 +#define PID_MASK_HI 0x1F +//flags +#define TRANS_SCRMBL1 0x80 +#define TRANS_SCRMBL2 0x40 +#define ADAPT_FIELD 0x20 +#define PAYLOAD 0x10 +#define COUNT_MASK 0x0F + +// adaptation flags +#define DISCON_IND 0x80 +#define RAND_ACC_IND 0x40 +#define ES_PRI_IND 0x20 +#define PCR_FLAG 0x10 +#define OPCR_FLAG 0x08 +#define SPLICE_FLAG 0x04 +#define TRANS_PRIV 0x02 +#define ADAP_EXT_FLAG 0x01 + +// adaptation extension flags +#define LTW_FLAG 0x80 +#define PIECE_RATE 0x40 +#define SEAM_SPLICE 0x20 + + +#define MAX_PLENGTH 0xFFFF +#define MMAX_PLENGTH (256*MAX_PLENGTH) + +#ifndef IPACKS +#define IPACKS 2048 +#endif + +struct ipack { + int size; + int found; + u8 *buf; + u8 cid; + u32 plength; + u8 plen[2]; + u8 flag1; + u8 flag2; + u8 hlength; + u8 pts[5]; + u16 *pid; + int mpeg; + u8 check; + int which; + int done; + void *data; + void (*func)(u8 *buf, int size, void *priv); + int count; + int repack_subids; +}; + +struct dvb_video_info { + u32 horizontal_size; + u32 vertical_size; + u32 aspect_ratio; + u32 framerate; + u32 video_format; + u32 bit_rate; + u32 comp_bit_rate; + u32 vbv_buffer_size; + s16 vbv_delay; + u32 CSPF; + u32 off; +}; + +#define OFF_SIZE 4 +#define FIRST_FIELD 0 +#define SECOND_FIELD 1 +#define VIDEO_FRAME_PICTURE 0x03 + +struct mpg_picture { + int channel; + struct dvb_video_info vinfo; + u32 *sequence_gop_header; + u32 *picture_header; + s32 time_code; + int low_delay; + int closed_gop; + int broken_link; + int sequence_header_flag; + int gop_flag; + int sequence_end_flag; + + u8 profile_and_level; + s32 picture_coding_parameter; + u32 matrix[32]; + s8 matrix_change_flag; + + u8 picture_header_parameter; + /* bit 0 - 2: bwd f code + bit 3 : fpb vector + bit 4 - 6: fwd f code + bit 7 : fpf vector */ + + int mpeg1_flag; + int progressive_sequence; + int sequence_display_extension_flag; + u32 sequence_header_data; + s16 last_frame_centre_horizontal_offset; + s16 last_frame_centre_vertical_offset; + + u32 pts[2]; /* [0] 1st field, [1] 2nd field */ + int top_field_first; + int repeat_first_field; + int progressive_frame; + int bank; + int forward_bank; + int backward_bank; + int compress; + s16 frame_centre_horizontal_offset[OFF_SIZE]; + /* [0-2] 1st field, [3] 2nd field */ + s16 frame_centre_vertical_offset[OFF_SIZE]; + /* [0-2] 1st field, [3] 2nd field */ + s16 temporal_reference[2]; + /* [0] 1st field, [1] 2nd field */ + + s8 picture_coding_type[2]; + /* [0] 1st field, [1] 2nd field */ + s8 picture_structure[2]; + /* [0] 1st field, [1] 2nd field */ + s8 picture_display_extension_flag[2]; + /* [0] 1st field, [1] 2nd field */ + /* picture_display_extenion() 0:no 1:exit*/ + s8 pts_flag[2]; + /* [0] 1st field, [1] 2nd field */ +}; + +struct dvb_audio_info { + int layer; + u32 bit_rate; + u32 frequency; + u32 mode; + u32 mode_extension ; + u32 emphasis; + u32 framesize; + u32 off; +}; + +int dvb_filter_get_ac3info(u8 *mbuf, int count, struct dvb_audio_info *ai, int pr); + + +#endif diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c new file mode 100644 index 000000000000..12e5eb1fff76 --- /dev/null +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -0,0 +1,2553 @@ +/* + * 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) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +/* Enables DVBv3 compatibility bits at the headers */ +#define __DVB_CORE__ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/semaphore.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/freezer.h> +#include <linux/jiffies.h> +#include <linux/kthread.h> +#include <asm/processor.h> + +#include "dvb_frontend.h" +#include "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 if (dvb_frontend_debug) printk + +#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) + +#define FE_ALGO_HW 1 +/* + * 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. + */ + +#define DVB_FE_NO_EXIT 0 +#define DVB_FE_NORMAL_EXIT 1 +#define DVB_FE_DEVICE_REMOVED 2 + +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 exit; + unsigned int wakeup; + fe_status_t 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; +}; + +static void dvb_frontend_wakeup(struct dvb_frontend *fe); +static int dtv_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p_out); +static int dtv_property_legacy_params_sync(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p); + +static bool has_get_frontend(struct dvb_frontend *fe) +{ + return fe->ops.get_frontend != NULL; +} + +/* + * 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, fe_status_t status) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + struct dvb_frontend_event *e; + int wp; + + dprintk ("%s\n", __func__); + + if ((status & FE_HAS_LOCK) && has_get_frontend(fe)) + dtv_get_frontend(fe, &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_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; + + dprintk ("%s\n", __func__); + + if (events->overflow) { + events->overflow = 0; + return -EOVERFLOW; + } + + if (events->eventw == events->eventr) { + int ret; + + if (flags & O_NONBLOCK) + return -EWOULDBLOCK; + + up(&fepriv->sem); + + ret = wait_event_interruptible (events->wait_queue, + events->eventw != events->eventr); + + if (down_interruptible (&fepriv->sem)) + return -ERESTARTSYS; + + 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) +{ + dprintk ("DVB: initialising adapter %i frontend %i (%s)...\n", + 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; + + dprintk ("%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); +} + +/** + * Performs automatic twiddling of frontend parameters. + * + * @param fe The frontend concerned. + * @param check_wrapped Checks if an iteration has completed. DO NOT SET ON THE FIRST ATTEMPT + * @returns 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; + } + + dprintk("%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) +{ + fe_status_t s = 0; + int retval = 0; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache, tmp; + + /* 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 retuned, 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 (fepriv->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 dvb_frontend_private *fepriv = fe->frontend_priv; + fe_status_t s; + enum dvbfe_algo algo; + + bool re_tune = false; + + dprintk("%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 */ + fepriv->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: + dprintk("%s: Frontend ALGO = DVBFE_ALGO_HW\n", __func__); + + if (fepriv->state & FESTATE_RETUNE) { + dprintk("%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)) { + dprintk("%s: state changed, adding current state\n", __func__); + dvb_frontend_add_event(fe, s); + fepriv->status = s; + } + break; + case DVBFE_ALGO_SW: + dprintk("%s: Frontend ALGO = DVBFE_ALGO_SW\n", __func__); + dvb_frontend_swzigzag(fe); + break; + case DVBFE_ALGO_CUSTOM: + dprintk("%s: Frontend ALGO = DVBFE_ALGO_CUSTOM, state=%d\n", __func__, fepriv->state); + if (fepriv->state & FESTATE_RETUNE) { + dprintk("%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, &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: + dprintk("%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()) + fepriv->exit = DVB_FE_DEVICE_REMOVED; + else + fepriv->exit = DVB_FE_NO_EXIT; + mb(); + + dvb_frontend_wakeup(fe); + return 0; +} + +static void dvb_frontend_stop(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + dprintk ("%s\n", __func__); + + fepriv->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) + printk("dvb_frontend_stop: warning: thread %p won't exit\n", + fepriv->thread); +} + +s32 timeval_usec_diff(struct timeval lasttime, struct timeval curtime) +{ + return ((curtime.tv_usec < lasttime.tv_usec) ? + 1000000 - lasttime.tv_usec + curtime.tv_usec : + curtime.tv_usec - lasttime.tv_usec); +} +EXPORT_SYMBOL(timeval_usec_diff); + +static inline void timeval_usec_add(struct timeval *curtime, u32 add_usec) +{ + curtime->tv_usec += add_usec; + if (curtime->tv_usec >= 1000000) { + curtime->tv_usec -= 1000000; + curtime->tv_sec++; + } +} + +/* + * Sleep until gettimeofday() > waketime + add_usec + * This needs to be as precise as possible, but as the delay is + * usually between 2ms and 32ms, it is done using a scheduled msleep + * followed by usleep (normally a busy-wait loop) for the remainder + */ +void dvb_frontend_sleep_until(struct timeval *waketime, u32 add_usec) +{ + struct timeval lasttime; + s32 delta, newdelta; + + timeval_usec_add(waketime, add_usec); + + do_gettimeofday(&lasttime); + delta = timeval_usec_diff(lasttime, *waketime); + if (delta > 2500) { + msleep((delta - 1500) / 1000); + do_gettimeofday(&lasttime); + newdelta = timeval_usec_diff(lasttime, *waketime); + delta = (newdelta > delta) ? 0 : newdelta; + } + 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; + + dprintk ("%s\n", __func__); + + if (fepriv->thread) { + if (fepriv->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; + fepriv->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); + printk("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) +{ + *freq_min = max(fe->ops.info.frequency_min, fe->ops.tuner_ops.info.frequency_min); + + if (fe->ops.info.frequency_max == 0) + *freq_max = fe->ops.tuner_ops.info.frequency_max; + else if (fe->ops.tuner_ops.info.frequency_max == 0) + *freq_max = fe->ops.info.frequency_max; + else + *freq_max = min(fe->ops.info.frequency_max, fe->ops.tuner_ops.info.frequency_max); + + if (*freq_min == 0 || *freq_max == 0) + printk(KERN_WARNING "DVB: adapter %i frontend %u frequency limits undefined - fix the driver\n", + fe->dvb->num,fe->id); +} + +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); + if ((freq_min && c->frequency < freq_min) || + (freq_max && c->frequency > freq_max)) { + printk(KERN_WARNING "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)) { + printk(KERN_WARNING "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; + } + 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, sizeof(struct dtv_frontend_properties)); + c->delivery_system = delsys; + + c->state = DTV_CLEAR; + + dprintk("%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 = 0; + 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->isdbs_ts_id = 0; + c->dvbt2_plp_id = 0; + + 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; + default: + c->modulation = QAM_AUTO; + break; + } + + return 0; +} + +#define _DTV_CMD(n, s, b) \ +[n] = { \ + .name = #n, \ + .cmd = n, \ + .set = s,\ + .buffer = b \ +} + +static struct dtv_cmds_h dtv_cmds[DTV_MAX_COMMAND + 1] = { + _DTV_CMD(DTV_TUNE, 1, 0), + _DTV_CMD(DTV_CLEAR, 1, 0), + + /* Set */ + _DTV_CMD(DTV_FREQUENCY, 1, 0), + _DTV_CMD(DTV_BANDWIDTH_HZ, 1, 0), + _DTV_CMD(DTV_MODULATION, 1, 0), + _DTV_CMD(DTV_INVERSION, 1, 0), + _DTV_CMD(DTV_DISEQC_MASTER, 1, 1), + _DTV_CMD(DTV_SYMBOL_RATE, 1, 0), + _DTV_CMD(DTV_INNER_FEC, 1, 0), + _DTV_CMD(DTV_VOLTAGE, 1, 0), + _DTV_CMD(DTV_TONE, 1, 0), + _DTV_CMD(DTV_PILOT, 1, 0), + _DTV_CMD(DTV_ROLLOFF, 1, 0), + _DTV_CMD(DTV_DELIVERY_SYSTEM, 1, 0), + _DTV_CMD(DTV_HIERARCHY, 1, 0), + _DTV_CMD(DTV_CODE_RATE_HP, 1, 0), + _DTV_CMD(DTV_CODE_RATE_LP, 1, 0), + _DTV_CMD(DTV_GUARD_INTERVAL, 1, 0), + _DTV_CMD(DTV_TRANSMISSION_MODE, 1, 0), + _DTV_CMD(DTV_INTERLEAVING, 1, 0), + + _DTV_CMD(DTV_ISDBT_PARTIAL_RECEPTION, 1, 0), + _DTV_CMD(DTV_ISDBT_SOUND_BROADCASTING, 1, 0), + _DTV_CMD(DTV_ISDBT_SB_SUBCHANNEL_ID, 1, 0), + _DTV_CMD(DTV_ISDBT_SB_SEGMENT_IDX, 1, 0), + _DTV_CMD(DTV_ISDBT_SB_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYER_ENABLED, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_FEC, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_MODULATION, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_TIME_INTERLEAVING, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_FEC, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_MODULATION, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_TIME_INTERLEAVING, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_FEC, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_MODULATION, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_TIME_INTERLEAVING, 1, 0), + + _DTV_CMD(DTV_ISDBS_TS_ID, 1, 0), + _DTV_CMD(DTV_DVBT2_PLP_ID, 1, 0), + + /* Get */ + _DTV_CMD(DTV_DISEQC_SLAVE_REPLY, 0, 1), + _DTV_CMD(DTV_API_VERSION, 0, 0), + _DTV_CMD(DTV_CODE_RATE_HP, 0, 0), + _DTV_CMD(DTV_CODE_RATE_LP, 0, 0), + _DTV_CMD(DTV_GUARD_INTERVAL, 0, 0), + _DTV_CMD(DTV_TRANSMISSION_MODE, 0, 0), + _DTV_CMD(DTV_HIERARCHY, 0, 0), + _DTV_CMD(DTV_INTERLEAVING, 0, 0), + + _DTV_CMD(DTV_ENUM_DELSYS, 0, 0), + + _DTV_CMD(DTV_ATSCMH_PARADE_ID, 1, 0), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_ENSEMBLE, 1, 0), + + _DTV_CMD(DTV_ATSCMH_FIC_VER, 0, 0), + _DTV_CMD(DTV_ATSCMH_PARADE_ID, 0, 0), + _DTV_CMD(DTV_ATSCMH_NOG, 0, 0), + _DTV_CMD(DTV_ATSCMH_TNOG, 0, 0), + _DTV_CMD(DTV_ATSCMH_SGN, 0, 0), + _DTV_CMD(DTV_ATSCMH_PRC, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_MODE, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_ENSEMBLE, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_PRI, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_SEC, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_BLOCK_MODE, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_A, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_B, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_C, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_D, 0, 0), +}; + +static void dtv_property_dump(struct dtv_property *tvp) +{ + int i; + + if (tvp->cmd <= 0 || tvp->cmd > DTV_MAX_COMMAND) { + printk(KERN_WARNING "%s: tvp.cmd = 0x%08x undefined\n", + __func__, tvp->cmd); + return; + } + + dprintk("%s() tvp.cmd = 0x%08x (%s)\n" + ,__func__ + ,tvp->cmd + ,dtv_cmds[ tvp->cmd ].name); + + if(dtv_cmds[ tvp->cmd ].buffer) { + + dprintk("%s() tvp.u.buffer.len = 0x%02x\n" + ,__func__ + ,tvp->u.buffer.len); + + for(i = 0; i < tvp->u.buffer.len; i++) + dprintk("%s() tvp.u.buffer.data[0x%02x] = 0x%02x\n" + ,__func__ + ,i + ,tvp->u.buffer.data[i]); + + } else + dprintk("%s() tvp.u.data = 0x%08x\n", __func__, tvp->u.data); +} + +/* 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: + dprintk("%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: + dprintk("%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: + dprintk("%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: + dprintk("%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: + printk(KERN_ERR + "%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, + struct dvb_frontend_parameters *p) +{ + const struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + p->frequency = c->frequency; + p->inversion = c->inversion; + + switch (dvbv3_type(c->delivery_system)) { + case DVBV3_UNKNOWN: + printk(KERN_ERR + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + return -EINVAL; + case DVBV3_QPSK: + dprintk("%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: + dprintk("%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: + dprintk("%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: + dprintk("%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 dvb_frontend_parameters *p_out) +{ + int r; + + if (fe->ops.get_frontend) { + r = fe->ops.get_frontend(fe); + if (unlikely(r < 0)) + return r; + if (p_out) + dtv_property_legacy_params_sync(fe, p_out); + return 0; + } + + /* As everything is in cache, get_frontend fops are always supported */ + return 0; +} + +static int dvb_frontend_ioctl_legacy(struct file *file, + unsigned int cmd, void *parg); +static int dvb_frontend_ioctl_properties(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 r, ncaps; + + switch(tvp->cmd) { + case DTV_ENUM_DELSYS: + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + tvp->u.buffer.data[ncaps] = fe->ops.delsys[ncaps]; + ncaps++; + } + tvp->u.buffer.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; + case DTV_ISDBS_TS_ID: + tvp->u.data = c->isdbs_ts_id; + break; + case DTV_DVBT2_PLP_ID: + tvp->u.data = c->dvbt2_plp_id; + 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; + + default: + return -EINVAL; + } + + /* Allow the frontend to override outgoing properties */ + if (fe->ops.get_property) { + r = fe->ops.get_property(fe, tvp); + if (r < 0) + return r; + } + + dtv_property_dump(tvp); + + return 0; +} + +static int dtv_set_frontend(struct dvb_frontend *fe); + +static bool is_dvbv3_delsys(u32 delsys) +{ + bool status; + + status = (delsys == SYS_DVBT) || (delsys == SYS_DVBC_ANNEX_A) || + (delsys == SYS_DVBS) || (delsys == SYS_ATSC); + + return status; +} + +static int set_delivery_system(struct dvb_frontend *fe, u32 desired_system) +{ + int ncaps, i; + 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 (c->delivery_system == SYS_UNDEFINED) + c->delivery_system = fe->ops.delsys[0]; + + if (desired_system == SYS_UNDEFINED) { + /* + * A DVBv3 call doesn't know what's the desired system. + * Also, DVBv3 applications don't know that ops.info->type + * could be changed, and they simply dies when it doesn't + * match. + * So, don't change the current delivery system, as it + * may be trying to do the wrong thing, like setting an + * ISDB-T frontend as DVB-T. Instead, find the closest + * DVBv3 system that matches the delivery system. + */ + if (is_dvbv3_delsys(c->delivery_system)) { + dprintk("%s() Using delivery system to %d\n", + __func__, c->delivery_system); + return 0; + } + type = dvbv3_type(c->delivery_system); + switch (type) { + case DVBV3_QPSK: + desired_system = SYS_DVBS; + break; + case DVBV3_QAM: + desired_system = SYS_DVBC_ANNEX_A; + break; + case DVBV3_ATSC: + desired_system = SYS_ATSC; + break; + case DVBV3_OFDM: + desired_system = SYS_DVBT; + break; + default: + dprintk("%s(): This frontend doesn't support DVBv3 calls\n", + __func__); + return -EINVAL; + } + /* + * Get a delivery system that is compatible with DVBv3 + * NOTE: in order for this to work with softwares like Kaffeine that + * uses a DVBv5 call for DVB-S2 and a DVBv3 call to go back to + * DVB-S, drivers that support both should put the SYS_DVBS entry + * before the SYS_DVBS2, otherwise it won't switch back to DVB-S. + * The real fix is that userspace applications should not use DVBv3 + * and not trust on calling FE_SET_FRONTEND to switch the delivery + * system. + */ + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + if (fe->ops.delsys[ncaps] == desired_system) { + delsys = desired_system; + break; + } + ncaps++; + } + if (delsys == SYS_UNDEFINED) { + dprintk("%s() Couldn't find a delivery system that matches %d\n", + __func__, desired_system); + } + } else { + /* + * This is a DVBv5 call. So, it likely knows the supported + * delivery systems. + */ + + /* Check if the desired delivery system is supported */ + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + if (fe->ops.delsys[ncaps] == desired_system) { + c->delivery_system = desired_system; + dprintk("%s() Changing delivery system to %d\n", + __func__, desired_system); + return 0; + } + ncaps++; + } + type = dvbv3_type(desired_system); + + /* + * The delivery system is not supported. See if it can be + * emulated. + * The emulation only works if the desired system is one of the + * DVBv3 delivery systems + */ + if (!is_dvbv3_delsys(desired_system)) { + dprintk("%s() can't use a DVBv3 FE_SET_FRONTEND call on this frontend\n", + __func__); + return -EINVAL; + } + + /* + * Get the last non-DVBv3 delivery system that has the same type + * of the desired system + */ + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + if ((dvbv3_type(fe->ops.delsys[ncaps]) == type) && + !is_dvbv3_delsys(fe->ops.delsys[ncaps])) + delsys = fe->ops.delsys[ncaps]; + ncaps++; + } + /* There's nothing compatible with the desired delivery system */ + if (delsys == SYS_UNDEFINED) { + dprintk("%s() Incompatible DVBv3 FE_SET_FRONTEND call for this frontend\n", + __func__); + return -EINVAL; + } + } + + c->delivery_system = delsys; + + /* + * The DVBv3 or DVBv5 call is requesting a different system. So, + * emulation is needed. + * + * Emulate newer delivery systems like ISDBT, DVBT and DTMB + * for older DVBv5 applications. The emulation will try to use + * the auto mode for most things, and will assume that the desired + * delivery system is the last one at the ops.delsys[] array + */ + dprintk("%s() Using delivery system %d emulated as if it were a %d\n", + __func__, delsys, desired_system); + + /* + * For now, handles ISDB-T calls. More code may be needed here for the + * other emulated stuff + */ + if (type == DVBV3_OFDM) { + if (c->delivery_system == SYS_ISDBT) { + dprintk("%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 = 0; + 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; + } + } + } + dprintk("change delivery system on cache to %d\n", c->delivery_system); + + return 0; +} + +static int dtv_property_process_set(struct dvb_frontend *fe, + struct dtv_property *tvp, + struct file *file) +{ + int r = 0; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + /* Allow the frontend to validate incoming properties */ + if (fe->ops.set_property) { + r = fe->ops.set_property(fe, tvp); + if (r < 0) + return r; + } + + switch(tvp->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: + /* interpret the cache of data, build either a traditional frontend + * tunerequest so we can pass validation in the FE_SET_FRONTEND + * ioctl. + */ + c->state = tvp->cmd; + dprintk("%s() Finalised property cache\n", __func__); + + r = dtv_set_frontend(fe); + break; + case DTV_FREQUENCY: + c->frequency = tvp->u.data; + break; + case DTV_MODULATION: + c->modulation = tvp->u.data; + break; + case DTV_BANDWIDTH_HZ: + c->bandwidth_hz = tvp->u.data; + break; + case DTV_INVERSION: + c->inversion = tvp->u.data; + break; + case DTV_SYMBOL_RATE: + c->symbol_rate = tvp->u.data; + break; + case DTV_INNER_FEC: + c->fec_inner = tvp->u.data; + break; + case DTV_PILOT: + c->pilot = tvp->u.data; + break; + case DTV_ROLLOFF: + c->rolloff = tvp->u.data; + break; + case DTV_DELIVERY_SYSTEM: + r = set_delivery_system(fe, tvp->u.data); + break; + case DTV_VOLTAGE: + c->voltage = tvp->u.data; + r = dvb_frontend_ioctl_legacy(file, FE_SET_VOLTAGE, + (void *)c->voltage); + break; + case DTV_TONE: + c->sectone = tvp->u.data; + r = dvb_frontend_ioctl_legacy(file, FE_SET_TONE, + (void *)c->sectone); + break; + case DTV_CODE_RATE_HP: + c->code_rate_HP = tvp->u.data; + break; + case DTV_CODE_RATE_LP: + c->code_rate_LP = tvp->u.data; + break; + case DTV_GUARD_INTERVAL: + c->guard_interval = tvp->u.data; + break; + case DTV_TRANSMISSION_MODE: + c->transmission_mode = tvp->u.data; + break; + case DTV_HIERARCHY: + c->hierarchy = tvp->u.data; + break; + case DTV_INTERLEAVING: + c->interleaving = tvp->u.data; + break; + + /* ISDB-T Support here */ + case DTV_ISDBT_PARTIAL_RECEPTION: + c->isdbt_partial_reception = tvp->u.data; + break; + case DTV_ISDBT_SOUND_BROADCASTING: + c->isdbt_sb_mode = tvp->u.data; + break; + case DTV_ISDBT_SB_SUBCHANNEL_ID: + c->isdbt_sb_subchannel = tvp->u.data; + break; + case DTV_ISDBT_SB_SEGMENT_IDX: + c->isdbt_sb_segment_idx = tvp->u.data; + break; + case DTV_ISDBT_SB_SEGMENT_COUNT: + c->isdbt_sb_segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYER_ENABLED: + c->isdbt_layer_enabled = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_FEC: + c->layer[0].fec = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_MODULATION: + c->layer[0].modulation = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_SEGMENT_COUNT: + c->layer[0].segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_TIME_INTERLEAVING: + c->layer[0].interleaving = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_FEC: + c->layer[1].fec = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_MODULATION: + c->layer[1].modulation = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_SEGMENT_COUNT: + c->layer[1].segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_TIME_INTERLEAVING: + c->layer[1].interleaving = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_FEC: + c->layer[2].fec = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_MODULATION: + c->layer[2].modulation = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_SEGMENT_COUNT: + c->layer[2].segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_TIME_INTERLEAVING: + c->layer[2].interleaving = tvp->u.data; + break; + case DTV_ISDBS_TS_ID: + c->isdbs_ts_id = tvp->u.data; + break; + case DTV_DVBT2_PLP_ID: + c->dvbt2_plp_id = tvp->u.data; + break; + + /* ATSC-MH */ + case DTV_ATSCMH_PARADE_ID: + fe->dtv_property_cache.atscmh_parade_id = tvp->u.data; + break; + case DTV_ATSCMH_RS_FRAME_ENSEMBLE: + fe->dtv_property_cache.atscmh_rs_frame_ensemble = tvp->u.data; + break; + + default: + return -EINVAL; + } + + return r; +} + +static int dvb_frontend_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int err = -EOPNOTSUPP; + + dprintk("%s (%d)\n", __func__, _IOC_NR(cmd)); + + if (fepriv->exit != DVB_FE_NO_EXIT) + return -ENODEV; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY && + (_IOC_DIR(cmd) != _IOC_READ || cmd == FE_GET_EVENT || + cmd == FE_DISEQC_RECV_SLAVE_REPLY)) + return -EPERM; + + if (down_interruptible (&fepriv->sem)) + return -ERESTARTSYS; + + if ((cmd == FE_SET_PROPERTY) || (cmd == FE_GET_PROPERTY)) + err = dvb_frontend_ioctl_properties(file, cmd, parg); + else { + c->state = DTV_UNDEFINED; + err = dvb_frontend_ioctl_legacy(file, cmd, parg); + } + + up(&fepriv->sem); + return err; +} + +static int dvb_frontend_ioctl_properties(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 err = 0; + + struct dtv_properties *tvps = NULL; + struct dtv_property *tvp = NULL; + int i; + + dprintk("%s\n", __func__); + + if(cmd == FE_SET_PROPERTY) { + tvps = (struct dtv_properties __user *)parg; + + dprintk("%s() properties.num = %d\n", __func__, tvps->num); + dprintk("%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 == 0) || (tvps->num > DTV_IOCTL_MAX_MSGS)) + return -EINVAL; + + tvp = kmalloc(tvps->num * sizeof(struct dtv_property), GFP_KERNEL); + if (!tvp) { + err = -ENOMEM; + goto out; + } + + if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_set(fe, tvp + i, file); + if (err < 0) + goto out; + (tvp + i)->result = err; + } + + if (c->state == DTV_TUNE) + dprintk("%s() Property cache is full, tuning\n", __func__); + + } else + if(cmd == FE_GET_PROPERTY) { + tvps = (struct dtv_properties __user *)parg; + + dprintk("%s() properties.num = %d\n", __func__, tvps->num); + dprintk("%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 == 0) || (tvps->num > DTV_IOCTL_MAX_MSGS)) + return -EINVAL; + + tvp = kmalloc(tvps->num * sizeof(struct dtv_property), GFP_KERNEL); + if (!tvp) { + err = -ENOMEM; + goto out; + } + + if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + /* + * Fills the cache out struct with the cache contents, plus + * the data retrieved from get_frontend, if the frontend + * is not idle. Otherwise, returns the cached content + */ + if (fepriv->state != FESTATE_IDLE) { + err = dtv_get_frontend(fe, NULL); + if (err < 0) + goto out; + } + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_get(fe, c, tvp + i, file); + if (err < 0) + goto out; + (tvp + i)->result = err; + } + + if (copy_to_user(tvps->props, tvp, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + } else + err = -EOPNOTSUPP; + +out: + kfree(tvp); + return err; +} + +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; + struct dvb_frontend_tune_settings fetunesettings; + 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, &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; + default: + break; + } + if (rolloff) + c->bandwidth_hz = (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; + + /* get frontend-specific tuning settings */ + memset(&fetunesettings, 0, sizeof(struct dvb_frontend_tune_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 = fe->ops.info.frequency_stepsize * 2; + fepriv->max_drift = (fe->ops.info.frequency_stepsize * 2) + 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; + + 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_frontend_ioctl_legacy(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 err = -EOPNOTSUPP; + + switch (cmd) { + case FE_GET_INFO: { + struct dvb_frontend_info* info = parg; + + memcpy(info, &fe->ops.info, sizeof(struct dvb_frontend_info)); + dvb_frontend_get_frequency_limits(fe, &info->frequency_min, &info->frequency_max); + + /* + * 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: + printk(KERN_ERR + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + fe->ops.info.type = FE_OFDM; + } + dprintk("current delivery system on cache: %d, V3 type: %d\n", + c->delivery_system, fe->ops.info.type); + + /* Force the CAN_INVERSION_AUTO bit on. If the frontend doesn't + * do it, it is done for it. */ + info->caps |= FE_CAN_INVERSION_AUTO; + err = 0; + break; + } + + case FE_READ_STATUS: { + fe_status_t* 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_READ_BER: + if (fe->ops.read_ber) + err = fe->ops.read_ber(fe, (__u32*) parg); + break; + + case FE_READ_SIGNAL_STRENGTH: + if (fe->ops.read_signal_strength) + err = fe->ops.read_signal_strength(fe, (__u16*) parg); + break; + + case FE_READ_SNR: + if (fe->ops.read_snr) + err = fe->ops.read_snr(fe, (__u16*) parg); + break; + + case FE_READ_UNCORRECTED_BLOCKS: + if (fe->ops.read_ucblocks) + err = fe->ops.read_ucblocks(fe, (__u32*) parg); + 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) { + err = fe->ops.diseqc_send_master_cmd(fe, (struct dvb_diseqc_master_cmd*) parg); + 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, (fe_sec_mini_cmd_t) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_TONE: + if (fe->ops.set_tone) { + err = fe->ops.set_tone(fe, (fe_sec_tone_mode_t) parg); + fepriv->tone = (fe_sec_tone_mode_t) parg; + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_VOLTAGE: + if (fe->ops.set_voltage) { + err = fe->ops.set_voltage(fe, (fe_sec_voltage_t) parg); + fepriv->voltage = (fe_sec_voltage_t) parg; + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + 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; + struct timeval nexttime; + struct timeval tv[10]; + int i; + u8 last = 1; + if (dvb_frontend_debug) + printk("%s switch command: 0x%04lx\n", __func__, swcmd); + do_gettimeofday(&nexttime); + if (dvb_frontend_debug) + memcpy(&tv[0], &nexttime, sizeof(struct timeval)); + /* 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) + do_gettimeofday(&tv[i + 1]); + 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) { + printk("%s(%d): switch delay (should be 32k followed by all 8k\n", + __func__, fe->dvb->num); + for (i = 1; i < 10; i++) + printk("%d: %d\n", i, timeval_usec_diff(tv[i-1] , tv[i])); + } + err = 0; + 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, (struct dvb_diseqc_slave_reply*) 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: + err = set_delivery_system(fe, SYS_UNDEFINED); + 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 = dtv_get_frontend(fe, parg); + break; + + case FE_SET_FRONTEND_TUNE_MODE: + fepriv->tune_mode_flags = (unsigned long) parg; + err = 0; + break; + }; + + return err; +} + + +static unsigned int 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; + + dprintk ("%s\n", __func__); + + poll_wait (file, &fepriv->events.wait_queue, wait); + + if (fepriv->events.eventw != fepriv->events.eventr) + return (POLLIN | POLLRDNORM | POLLPRI); + + 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; + + dprintk ("%s\n", __func__); + if (fepriv->exit == DVB_FE_DEVICE_REMOVED) + return -ENODEV; + + if (adapter->mfe_shared) { + mutex_lock (&adapter->mfe_lock); + + if (adapter->mfe_dvbdev == NULL) + 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 != NULL)) { + 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 != NULL) { + 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; + + ret = dvb_frontend_start (fe); + if (ret) + goto err2; + + /* empty event queue */ + fepriv->events.eventr = fepriv->events.eventw = 0; + } + + if (adapter->mfe_shared) + mutex_unlock (&adapter->mfe_lock); + return ret; + +err2: + 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; + + dprintk ("%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); + if (fepriv->exit != DVB_FE_NO_EXIT) { + fops_put(file->f_op); + file->f_op = NULL; + wake_up(&dvbdev->wait_queue); + } + if (fe->ops.ts_bus_ctrl) + fe->ops.ts_bus_ctrl(fe, 0); + } + + return ret; +} + +static const struct file_operations dvb_frontend_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dvb_generic_ioctl, + .poll = dvb_frontend_poll, + .open = dvb_frontend_open, + .release = dvb_frontend_release, + .llseek = noop_llseek, +}; + +int dvb_register_frontend(struct dvb_adapter* dvb, + struct dvb_frontend* fe) +{ + struct dvb_frontend_private *fepriv; + static const struct dvb_device dvbdev_template = { + .users = ~0, + .writers = 1, + .readers = (~0)-1, + .fops = &dvb_frontend_fops, + .kernel_ioctl = dvb_frontend_ioctl + }; + + dprintk ("%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 == NULL) { + mutex_unlock(&frontend_mutex); + return -ENOMEM; + } + fepriv = fe->frontend_priv; + + 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; + + printk ("DVB: registering adapter %i frontend %i (%s)...\n", + fe->dvb->num, + fe->id, + fe->ops.info.name); + + dvb_register_device (fe->dvb, &fepriv->dvbdev, &dvbdev_template, + fe, DVB_DEVICE_FRONTEND); + + /* + * 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; + dprintk ("%s\n", __func__); + + mutex_lock(&frontend_mutex); + dvb_frontend_stop (fe); + mutex_unlock(&frontend_mutex); + + if (fepriv->dvbdev->users < -1) + wait_event(fepriv->dvbdev->wait_queue, + fepriv->dvbdev->users==-1); + + mutex_lock(&frontend_mutex); + dvb_unregister_device (fepriv->dvbdev); + + /* fe is invalid now */ + kfree(fepriv); + mutex_unlock(&frontend_mutex); + return 0; +} +EXPORT_SYMBOL(dvb_unregister_frontend); + +#ifdef CONFIG_MEDIA_ATTACH +void dvb_frontend_detach(struct dvb_frontend* fe) +{ + void *ptr; + + if (fe->ops.release_sec) { + fe->ops.release_sec(fe); + symbol_put_addr(fe->ops.release_sec); + } + if (fe->ops.tuner_ops.release) { + fe->ops.tuner_ops.release(fe); + symbol_put_addr(fe->ops.tuner_ops.release); + } + if (fe->ops.analog_ops.release) { + fe->ops.analog_ops.release(fe); + symbol_put_addr(fe->ops.analog_ops.release); + } + ptr = (void*)fe->ops.release; + if (ptr) { + fe->ops.release(fe); + symbol_put_addr(ptr); + } +} +#else +void dvb_frontend_detach(struct dvb_frontend* fe) +{ + if (fe->ops.release_sec) + fe->ops.release_sec(fe); + if (fe->ops.tuner_ops.release) + fe->ops.tuner_ops.release(fe); + if (fe->ops.analog_ops.release) + fe->ops.analog_ops.release(fe); + if (fe->ops.release) + fe->ops.release(fe); +} +#endif +EXPORT_SYMBOL(dvb_frontend_detach); diff --git a/drivers/media/dvb-core/dvb_frontend.h b/drivers/media/dvb-core/dvb_frontend.h new file mode 100644 index 000000000000..de410cc94fbb --- /dev/null +++ b/drivers/media/dvb-core/dvb_frontend.h @@ -0,0 +1,425 @@ +/* + * dvb_frontend.h + * + * Copyright (C) 2001 convergence integrated media GmbH + * Copyright (C) 2004 convergence GmbH + * + * Written by Ralph Metzler + * Overhauled by Holger Waechtler + * Kernel I2C stuff by Michael Hunold <hunold@convergence.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DVB_FRONTEND_H_ +#define _DVB_FRONTEND_H_ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <linux/dvb/frontend.h> + +#include "dvbdev.h" + +/* + * Maximum number of Delivery systems per frontend. It + * should be smaller or equal to 32 + */ +#define MAX_DELSYS 8 + +struct dvb_frontend_tune_settings { + int min_delay_ms; + int step_size; + int max_drift; +}; + +struct dvb_frontend; + +struct dvb_tuner_info { + char name[128]; + + u32 frequency_min; + u32 frequency_max; + u32 frequency_step; + + u32 bandwidth_min; + u32 bandwidth_max; + u32 bandwidth_step; +}; + +struct analog_parameters { + unsigned int frequency; + unsigned int mode; + unsigned int audmode; + u64 std; +}; + +enum dvbfe_modcod { + DVBFE_MODCOD_DUMMY_PLFRAME = 0, + DVBFE_MODCOD_QPSK_1_4, + DVBFE_MODCOD_QPSK_1_3, + DVBFE_MODCOD_QPSK_2_5, + DVBFE_MODCOD_QPSK_1_2, + DVBFE_MODCOD_QPSK_3_5, + DVBFE_MODCOD_QPSK_2_3, + DVBFE_MODCOD_QPSK_3_4, + DVBFE_MODCOD_QPSK_4_5, + DVBFE_MODCOD_QPSK_5_6, + DVBFE_MODCOD_QPSK_8_9, + DVBFE_MODCOD_QPSK_9_10, + DVBFE_MODCOD_8PSK_3_5, + DVBFE_MODCOD_8PSK_2_3, + DVBFE_MODCOD_8PSK_3_4, + DVBFE_MODCOD_8PSK_5_6, + DVBFE_MODCOD_8PSK_8_9, + DVBFE_MODCOD_8PSK_9_10, + DVBFE_MODCOD_16APSK_2_3, + DVBFE_MODCOD_16APSK_3_4, + DVBFE_MODCOD_16APSK_4_5, + DVBFE_MODCOD_16APSK_5_6, + DVBFE_MODCOD_16APSK_8_9, + DVBFE_MODCOD_16APSK_9_10, + DVBFE_MODCOD_32APSK_3_4, + DVBFE_MODCOD_32APSK_4_5, + DVBFE_MODCOD_32APSK_5_6, + DVBFE_MODCOD_32APSK_8_9, + DVBFE_MODCOD_32APSK_9_10, + DVBFE_MODCOD_RESERVED_1, + DVBFE_MODCOD_BPSK_1_3, + DVBFE_MODCOD_BPSK_1_4, + DVBFE_MODCOD_RESERVED_2 +}; + +enum tuner_param { + DVBFE_TUNER_FREQUENCY = (1 << 0), + DVBFE_TUNER_TUNERSTEP = (1 << 1), + DVBFE_TUNER_IFFREQ = (1 << 2), + DVBFE_TUNER_BANDWIDTH = (1 << 3), + DVBFE_TUNER_REFCLOCK = (1 << 4), + DVBFE_TUNER_IQSENSE = (1 << 5), + DVBFE_TUNER_DUMMY = (1 << 31) +}; + +/* + * ALGO_HW: (Hardware Algorithm) + * ---------------------------------------------------------------- + * Devices that support this algorithm do everything in hardware + * and no software support is needed to handle them. + * Requesting these devices to LOCK is the only thing required, + * device is supposed to do everything in the hardware. + * + * ALGO_SW: (Software Algorithm) + * ---------------------------------------------------------------- + * These are dumb devices, that require software to do everything + * + * ALGO_CUSTOM: (Customizable Agorithm) + * ---------------------------------------------------------------- + * Devices having this algorithm can be customized to have specific + * algorithms in the frontend driver, rather than simply doing a + * software zig-zag. In this case the zigzag maybe hardware assisted + * or it maybe completely done in hardware. In all cases, usage of + * this algorithm, in conjunction with the search and track + * callbacks, utilizes the driver specific algorithm. + * + * ALGO_RECOVERY: (Recovery Algorithm) + * ---------------------------------------------------------------- + * These devices have AUTO recovery capabilities from LOCK failure + */ +enum dvbfe_algo { + DVBFE_ALGO_HW = (1 << 0), + DVBFE_ALGO_SW = (1 << 1), + DVBFE_ALGO_CUSTOM = (1 << 2), + DVBFE_ALGO_RECOVERY = (1 << 31) +}; + +struct tuner_state { + u32 frequency; + u32 tunerstep; + u32 ifreq; + u32 bandwidth; + u32 iqsense; + u32 refclock; +}; + +/* + * search callback possible return status + * + * DVBFE_ALGO_SEARCH_SUCCESS + * The frontend search algorithm completed and returned successfully + * + * DVBFE_ALGO_SEARCH_ASLEEP + * The frontend search algorithm is sleeping + * + * DVBFE_ALGO_SEARCH_FAILED + * The frontend search for a signal failed + * + * DVBFE_ALGO_SEARCH_INVALID + * The frontend search algorith was probably supplied with invalid + * parameters and the search is an invalid one + * + * DVBFE_ALGO_SEARCH_ERROR + * The frontend search algorithm failed due to some error + * + * DVBFE_ALGO_SEARCH_AGAIN + * The frontend search algorithm was requested to search again + */ +enum dvbfe_search { + DVBFE_ALGO_SEARCH_SUCCESS = (1 << 0), + DVBFE_ALGO_SEARCH_ASLEEP = (1 << 1), + DVBFE_ALGO_SEARCH_FAILED = (1 << 2), + DVBFE_ALGO_SEARCH_INVALID = (1 << 3), + DVBFE_ALGO_SEARCH_AGAIN = (1 << 4), + DVBFE_ALGO_SEARCH_ERROR = (1 << 31), +}; + + +struct dvb_tuner_ops { + + struct dvb_tuner_info info; + + int (*release)(struct dvb_frontend *fe); + int (*init)(struct dvb_frontend *fe); + int (*sleep)(struct dvb_frontend *fe); + + /** This is for simple PLLs - set all parameters in one go. */ + int (*set_params)(struct dvb_frontend *fe); + int (*set_analog_params)(struct dvb_frontend *fe, struct analog_parameters *p); + + /** This is support for demods like the mt352 - fills out the supplied buffer with what to write. */ + int (*calc_regs)(struct dvb_frontend *fe, u8 *buf, int buf_len); + + /** This is to allow setting tuner-specific configs */ + int (*set_config)(struct dvb_frontend *fe, void *priv_cfg); + + int (*get_frequency)(struct dvb_frontend *fe, u32 *frequency); + int (*get_bandwidth)(struct dvb_frontend *fe, u32 *bandwidth); + int (*get_if_frequency)(struct dvb_frontend *fe, u32 *frequency); + +#define TUNER_STATUS_LOCKED 1 +#define TUNER_STATUS_STEREO 2 + int (*get_status)(struct dvb_frontend *fe, u32 *status); + int (*get_rf_strength)(struct dvb_frontend *fe, u16 *strength); + int (*get_afc)(struct dvb_frontend *fe, s32 *afc); + + /** These are provided separately from set_params in order to facilitate silicon + * tuners which require sophisticated tuning loops, controlling each parameter separately. */ + int (*set_frequency)(struct dvb_frontend *fe, u32 frequency); + int (*set_bandwidth)(struct dvb_frontend *fe, u32 bandwidth); + + /* + * These are provided separately from set_params in order to facilitate silicon + * tuners which require sophisticated tuning loops, controlling each parameter separately. + */ + int (*set_state)(struct dvb_frontend *fe, enum tuner_param param, struct tuner_state *state); + int (*get_state)(struct dvb_frontend *fe, enum tuner_param param, struct tuner_state *state); +}; + +struct analog_demod_info { + char *name; +}; + +struct analog_demod_ops { + + struct analog_demod_info info; + + void (*set_params)(struct dvb_frontend *fe, + struct analog_parameters *params); + int (*has_signal)(struct dvb_frontend *fe); + int (*get_afc)(struct dvb_frontend *fe); + void (*tuner_status)(struct dvb_frontend *fe); + void (*standby)(struct dvb_frontend *fe); + void (*release)(struct dvb_frontend *fe); + int (*i2c_gate_ctrl)(struct dvb_frontend *fe, int enable); + + /** This is to allow setting tuner-specific configuration */ + int (*set_config)(struct dvb_frontend *fe, void *priv_cfg); +}; + +struct dtv_frontend_properties; + +struct dvb_frontend_ops { + + struct dvb_frontend_info info; + + u8 delsys[MAX_DELSYS]; + + void (*release)(struct dvb_frontend* fe); + void (*release_sec)(struct dvb_frontend* fe); + + int (*init)(struct dvb_frontend* fe); + int (*sleep)(struct dvb_frontend* fe); + + int (*write)(struct dvb_frontend* fe, const u8 buf[], int len); + + /* if this is set, it overrides the default swzigzag */ + int (*tune)(struct dvb_frontend* fe, + bool re_tune, + unsigned int mode_flags, + unsigned int *delay, + fe_status_t *status); + /* get frontend tuning algorithm from the module */ + enum dvbfe_algo (*get_frontend_algo)(struct dvb_frontend *fe); + + /* these two are only used for the swzigzag code */ + int (*set_frontend)(struct dvb_frontend *fe); + int (*get_tune_settings)(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* settings); + + int (*get_frontend)(struct dvb_frontend *fe); + + int (*read_status)(struct dvb_frontend* fe, fe_status_t* status); + int (*read_ber)(struct dvb_frontend* fe, u32* ber); + int (*read_signal_strength)(struct dvb_frontend* fe, u16* strength); + int (*read_snr)(struct dvb_frontend* fe, u16* snr); + int (*read_ucblocks)(struct dvb_frontend* fe, u32* ucblocks); + + int (*diseqc_reset_overload)(struct dvb_frontend* fe); + int (*diseqc_send_master_cmd)(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd); + int (*diseqc_recv_slave_reply)(struct dvb_frontend* fe, struct dvb_diseqc_slave_reply* reply); + int (*diseqc_send_burst)(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd); + int (*set_tone)(struct dvb_frontend* fe, fe_sec_tone_mode_t tone); + int (*set_voltage)(struct dvb_frontend* fe, fe_sec_voltage_t voltage); + int (*enable_high_lnb_voltage)(struct dvb_frontend* fe, long arg); + int (*dishnetwork_send_legacy_command)(struct dvb_frontend* fe, unsigned long cmd); + int (*i2c_gate_ctrl)(struct dvb_frontend* fe, int enable); + int (*ts_bus_ctrl)(struct dvb_frontend* fe, int acquire); + + /* These callbacks are for devices that implement their own + * tuning algorithms, rather than a simple swzigzag + */ + enum dvbfe_search (*search)(struct dvb_frontend *fe); + + struct dvb_tuner_ops tuner_ops; + struct analog_demod_ops analog_ops; + + int (*set_property)(struct dvb_frontend* fe, struct dtv_property* tvp); + int (*get_property)(struct dvb_frontend* fe, struct dtv_property* tvp); +}; + +#ifdef __DVB_CORE__ +#define MAX_EVENT 8 + +struct dvb_fe_events { + struct dvb_frontend_event events[MAX_EVENT]; + int eventw; + int eventr; + int overflow; + wait_queue_head_t wait_queue; + struct mutex mtx; +}; +#endif + +struct dtv_frontend_properties { + + /* Cache State */ + u32 state; + + u32 frequency; + fe_modulation_t modulation; + + fe_sec_voltage_t voltage; + fe_sec_tone_mode_t sectone; + fe_spectral_inversion_t inversion; + fe_code_rate_t fec_inner; + fe_transmit_mode_t transmission_mode; + u32 bandwidth_hz; /* 0 = AUTO */ + fe_guard_interval_t guard_interval; + fe_hierarchy_t hierarchy; + u32 symbol_rate; + fe_code_rate_t code_rate_HP; + fe_code_rate_t code_rate_LP; + + fe_pilot_t pilot; + fe_rolloff_t rolloff; + + fe_delivery_system_t delivery_system; + + enum fe_interleaving interleaving; + + /* ISDB-T specifics */ + u8 isdbt_partial_reception; + u8 isdbt_sb_mode; + u8 isdbt_sb_subchannel; + u32 isdbt_sb_segment_idx; + u32 isdbt_sb_segment_count; + u8 isdbt_layer_enabled; + struct { + u8 segment_count; + fe_code_rate_t fec; + fe_modulation_t modulation; + u8 interleaving; + } layer[3]; + + /* ISDB-T specifics */ + u32 isdbs_ts_id; + + /* DVB-T2 specifics */ + u32 dvbt2_plp_id; + + /* ATSC-MH specifics */ + u8 atscmh_fic_ver; + u8 atscmh_parade_id; + u8 atscmh_nog; + u8 atscmh_tnog; + u8 atscmh_sgn; + u8 atscmh_prc; + + u8 atscmh_rs_frame_mode; + u8 atscmh_rs_frame_ensemble; + u8 atscmh_rs_code_mode_pri; + u8 atscmh_rs_code_mode_sec; + u8 atscmh_sccc_block_mode; + u8 atscmh_sccc_code_mode_a; + u8 atscmh_sccc_code_mode_b; + u8 atscmh_sccc_code_mode_c; + u8 atscmh_sccc_code_mode_d; +}; + +struct dvb_frontend { + struct dvb_frontend_ops ops; + struct dvb_adapter *dvb; + void *demodulator_priv; + void *tuner_priv; + void *frontend_priv; + void *sec_priv; + void *analog_demod_priv; + struct dtv_frontend_properties dtv_property_cache; +#define DVB_FRONTEND_COMPONENT_TUNER 0 +#define DVB_FRONTEND_COMPONENT_DEMOD 1 + int (*callback)(void *adapter_priv, int component, int cmd, int arg); + int id; +}; + +extern int dvb_register_frontend(struct dvb_adapter *dvb, + struct dvb_frontend *fe); + +extern int dvb_unregister_frontend(struct dvb_frontend *fe); + +extern void dvb_frontend_detach(struct dvb_frontend *fe); + +extern void dvb_frontend_reinitialise(struct dvb_frontend *fe); + +extern void dvb_frontend_sleep_until(struct timeval *waketime, u32 add_usec); +extern s32 timeval_usec_diff(struct timeval lasttime, struct timeval curtime); + +#endif diff --git a/drivers/media/dvb-core/dvb_math.c b/drivers/media/dvb-core/dvb_math.c new file mode 100644 index 000000000000..beb7c93aa6cb --- /dev/null +++ b/drivers/media/dvb-core/dvb_math.c @@ -0,0 +1,145 @@ +/* + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/bug.h> +#include "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_math.h b/drivers/media/dvb-core/dvb_math.h new file mode 100644 index 000000000000..aecc867e9404 --- /dev/null +++ b/drivers/media/dvb-core/dvb_math.h @@ -0,0 +1,58 @@ +/* + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __DVB_MATH_H +#define __DVB_MATH_H + +#include <linux/types.h> + +/** + * computes log2 of a value; the result is shifted left by 24 bits + * + * to use rational values you can use the following method: + * intlog2(value) = intlog2(value * 2^x) - x * 2^24 + * + * example: intlog2(8) will give 3 << 24 = 3 * 2^24 + * example: intlog2(9) will give 3 << 24 + ... = 3.16... * 2^24 + * example: intlog2(1.5) = intlog2(3) - 2^24 = 0.584... * 2^24 + * + * @param value The value (must be != 0) + * @return log2(value) * 2^24 + */ +extern unsigned int intlog2(u32 value); + +/** + * computes log10 of a value; the result is shifted left by 24 bits + * + * to use rational values you can use the following method: + * intlog10(value) = intlog10(value * 10^x) - x * 2^24 + * + * example: intlog10(1000) will give 3 << 24 = 3 * 2^24 + * due to the implementation intlog10(1000) might be not exactly 3 * 2^24 + * + * look at intlog2 for similar examples + * + * @param value The value (must be != 0) + * @return log10(value) * 2^24 + */ +extern unsigned int intlog10(u32 value); + +#endif diff --git a/drivers/media/dvb-core/dvb_net.c b/drivers/media/dvb-core/dvb_net.c new file mode 100644 index 000000000000..8766ce8c354d --- /dev/null +++ b/drivers/media/dvb-core/dvb_net.c @@ -0,0 +1,1516 @@ +/* + * 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. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +/* + * 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) + * 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/dvb/net.h> +#include <linux/uio.h> +#include <asm/uaccess.h> +#include <linux/crc32.h> +#include <linux/mutex.h> +#include <linux/sched.h> + +#include "dvb_demux.h" +#include "dvb_net.h" + +static int dvb_net_debug; +module_param(dvb_net_debug, int, 0444); +MODULE_PARM_DESC(dvb_net_debug, "enable debug messages"); + +#define dprintk(x...) do { if (dvb_net_debug) printk(x); } while (0) + + +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 + +#undef ULE_DEBUG + +#ifdef ULE_DEBUG + +#define MAC_ADDR_PRINTFMT "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" +#define MAX_ADDR_PRINTFMT_ARGS(macap) (macap)[0],(macap)[1],(macap)[2],(macap)[3],(macap)[4],(macap)[5] + +#define isprint(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) + +static void hexdump( const unsigned char *buf, unsigned short len ) +{ + char str[80], octet[10]; + int ofs, i, l; + + for (ofs = 0; ofs < len; ofs += 16) { + sprintf( str, "%03d: ", ofs ); + + for (i = 0; i < 16; i++) { + if ((i + ofs) < len) + sprintf( octet, "%02x ", buf[ofs + i] ); + else + strcpy( octet, " " ); + + strcat( str, octet ); + } + strcat( str, " " ); + l = strlen( str ); + + for (i = 0; (i < 16) && ((i + ofs) < len); i++) + str[l++] = isprint( buf[ofs + i] ) ? buf[ofs + i] : '.'; + + str[l] = '\0'; + printk( KERN_WARNING "%s\n", str ); + } +} + +#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(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0) + skb->pkt_type=PACKET_BROADCAST; + else + skb->pkt_type=PACKET_MULTICAST; + } + + if (ntohs(eth->h_proto) >= 1536) + 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) < 1536) { + int framelen = p->ule_sndu_len - ((p->ule_next_hdr+sizeof(struct ethhdr)) - p->ule_skb->data); + /* A frame Type < 1536 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; +#ifdef ULE_DEBUG + dprintk("handle_ule_extensions: 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); +#endif + + } while (p->ule_sndu_type < 1536); + + 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. + */ +static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len ) +{ + struct dvb_net_priv *priv = netdev_priv(dev); + unsigned long skipped = 0L; + const u8 *ts, *ts_end, *from_where = NULL; + u8 ts_remain = 0, how_much = 0, new_ts = 1; + struct ethhdr *ethh = NULL; + bool error = false; + +#ifdef ULE_DEBUG + /* The code inside ULE_DEBUG keeps a history of the last 100 TS cells processed. */ + static unsigned char ule_hist[100*TS_SZ]; + static unsigned char *ule_where = ule_hist, ule_dump; +#endif + + /* For all TS cells in current buffer. + * Appearently, we are called for every single TS cell. + */ + for (ts = buf, ts_end = buf + buf_len; ts < ts_end; /* no default incr. */ ) { + + if (new_ts) { + /* We are about to process a new TS cell. */ + +#ifdef ULE_DEBUG + if (ule_where >= &ule_hist[100*TS_SZ]) ule_where = ule_hist; + memcpy( ule_where, ts, TS_SZ ); + if (ule_dump) { + hexdump( ule_where, TS_SZ ); + ule_dump = 0; + } + ule_where += TS_SZ; +#endif + + /* Check TS error conditions: sync_byte, transport_error_indicator, scrambling_control . */ + if ((ts[0] != TS_SYNC) || (ts[1] & TS_TEI) || ((ts[3] & TS_SC) != 0)) { + printk(KERN_WARNING "%lu: Invalid TS cell: SYNC %#x, TEI %u, SC %#x.\n", + priv->ts_count, ts[0], ts[1] & TS_TEI >> 7, ts[3] & 0xC0 >> 6); + + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (priv->ule_skb) { + dev_kfree_skb( priv->ule_skb ); + /* Prepare for next SNDU. */ + dev->stats.rx_errors++; + dev->stats.rx_frame_errors++; + } + reset_ule(priv); + priv->need_pusi = 1; + + /* Continue with next TS cell. */ + ts += TS_SZ; + priv->ts_count++; + continue; + } + + ts_remain = 184; + from_where = ts + 4; + } + /* Synchronize on PUSI, if required. */ + if (priv->need_pusi) { + if (ts[1] & TS_PUSI) { + /* Find beginning of first ULE SNDU in current TS cell. */ + /* Synchronize continuity counter. */ + priv->tscc = ts[3] & 0x0F; + /* There is a pointer field here. */ + if (ts[4] > ts_remain) { + printk(KERN_ERR "%lu: Invalid ULE packet " + "(pointer field %d)\n", priv->ts_count, ts[4]); + ts += TS_SZ; + priv->ts_count++; + continue; + } + /* Skip to destination of pointer field. */ + from_where = &ts[5] + ts[4]; + ts_remain -= 1 + ts[4]; + skipped = 0; + } else { + skipped++; + ts += TS_SZ; + priv->ts_count++; + continue; + } + } + + if (new_ts) { + /* Check continuity counter. */ + if ((ts[3] & 0x0F) == priv->tscc) + priv->tscc = (priv->tscc + 1) & 0x0F; + else { + /* TS discontinuity handling: */ + printk(KERN_WARNING "%lu: TS discontinuity: got %#x, " + "expected %#x.\n", priv->ts_count, ts[3] & 0x0F, priv->tscc); + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (priv->ule_skb) { + dev_kfree_skb( priv->ule_skb ); + /* Prepare for next SNDU. */ + // reset_ule(priv); moved to below. + dev->stats.rx_errors++; + dev->stats.rx_frame_errors++; + } + reset_ule(priv); + /* skip to next PUSI. */ + priv->need_pusi = 1; + continue; + } + /* 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 (ts[1] & TS_PUSI) { + if (! priv->need_pusi) { + if (!(*from_where < (ts_remain-1)) || *from_where != priv->ule_sndu_remain) { + /* Pointer field is invalid. Drop this TS cell and any started ULE SNDU. */ + printk(KERN_WARNING "%lu: Invalid pointer " + "field: %u.\n", priv->ts_count, *from_where); + + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (priv->ule_skb) { + error = true; + dev_kfree_skb(priv->ule_skb); + } + + if (error || priv->ule_sndu_remain) { + dev->stats.rx_errors++; + dev->stats.rx_frame_errors++; + error = false; + } + + reset_ule(priv); + priv->need_pusi = 1; + continue; + } + /* Skip pointer field (we're processing a + * packed payload). */ + from_where += 1; + ts_remain -= 1; + } else + priv->need_pusi = 0; + + if (priv->ule_sndu_remain > 183) { + /* Current SNDU lacks more data than there could be available in the + * current TS cell. */ + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + printk(KERN_WARNING "%lu: Expected %d more SNDU bytes, but " + "got PUSI (pf %d, ts_remain %d). Flushing incomplete payload.\n", + priv->ts_count, priv->ule_sndu_remain, ts[4], ts_remain); + dev_kfree_skb(priv->ule_skb); + /* Prepare for next SNDU. */ + reset_ule(priv); + /* Resync: go to where pointer field points to: start of next ULE SNDU. */ + from_where += ts[4]; + ts_remain -= ts[4]; + } + } + } + + /* Check if new payload needs to be started. */ + if (priv->ule_skb == NULL) { + /* 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 ts_remain has to be >= 2 here. */ + if (ts_remain < 2) { + printk(KERN_WARNING "Invalid payload packing: only %d " + "bytes left in TS. Resyncing.\n", ts_remain); + priv->ule_sndu_len = 0; + priv->need_pusi = 1; + ts += TS_SZ; + continue; + } + + if (! priv->ule_sndu_len) { + /* Got at least two bytes, thus extrace the SNDU length. */ + priv->ule_sndu_len = from_where[0] << 8 | from_where[1]; + if (priv->ule_sndu_len & 0x8000) { + /* D-Bit is set: no dest mac present. */ + priv->ule_sndu_len &= 0x7FFF; + priv->ule_dbit = 1; + } else + priv->ule_dbit = 0; + + if (priv->ule_sndu_len < 5) { + printk(KERN_WARNING "%lu: Invalid ULE SNDU length %u. " + "Resyncing.\n", priv->ts_count, priv->ule_sndu_len); + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + priv->ule_sndu_len = 0; + priv->need_pusi = 1; + new_ts = 1; + ts += TS_SZ; + priv->ts_count++; + continue; + } + ts_remain -= 2; /* consume the 2 bytes SNDU length. */ + from_where += 2; + } + + priv->ule_sndu_remain = priv->ule_sndu_len + 2; + /* + * State of current TS: + * 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 (ts_remain) { + case 1: + priv->ule_sndu_remain--; + priv->ule_sndu_type = from_where[0] << 8; + priv->ule_sndu_type_1 = 1; /* first byte of ule_type is set. */ + ts_remain -= 1; from_where += 1; + /* Continue w/ next TS. */ + case 0: + new_ts = 1; + ts += TS_SZ; + priv->ts_count++; + continue; + + default: /* complete ULE header is present in current TS. */ + /* Extract ULE type field. */ + if (priv->ule_sndu_type_1) { + priv->ule_sndu_type_1 = 0; + priv->ule_sndu_type |= from_where[0]; + from_where += 1; /* points to payload start. */ + ts_remain -= 1; + } else { + /* Complete type is present in new TS. */ + priv->ule_sndu_type = from_where[0] << 8 | from_where[1]; + from_where += 2; /* points to payload start. */ + 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). */ + priv->ule_skb = dev_alloc_skb( priv->ule_sndu_len + ETH_HLEN + ETH_ALEN ); + if (priv->ule_skb == NULL) { + printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", + dev->name); + dev->stats.rx_dropped++; + return; + } + + /* This includes the CRC32 _and_ dest mac, if !dbit. */ + priv->ule_sndu_remain = priv->ule_sndu_len; + priv->ule_skb->dev = dev; + /* Leave space for Ethernet or bridged SNDU header (eth hdr plus one MAC addr). */ + skb_reserve( priv->ule_skb, ETH_HLEN + ETH_ALEN ); + } + + /* Copy data into our current skb. */ + how_much = min(priv->ule_sndu_remain, (int)ts_remain); + memcpy(skb_put(priv->ule_skb, how_much), from_where, how_much); + priv->ule_sndu_remain -= how_much; + ts_remain -= how_much; + from_where += how_much; + + /* Check for complete payload. */ + if (priv->ule_sndu_remain <= 0) { + /* Check CRC32, we've got it in our skb already. */ + __be16 ulen = htons(priv->ule_sndu_len); + __be16 utype = htons(priv->ule_sndu_type); + const u8 *tail; + struct kvec iov[3] = { + { &ulen, sizeof ulen }, + { &utype, sizeof utype }, + { priv->ule_skb->data, priv->ule_skb->len - 4 } + }; + u32 ule_crc = ~0L, expected_crc; + if (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(priv->ule_skb); + expected_crc = *(tail - 4) << 24 | + *(tail - 3) << 16 | + *(tail - 2) << 8 | + *(tail - 1); + if (ule_crc != expected_crc) { + printk(KERN_WARNING "%lu: CRC32 check FAILED: %08x / %08x, SNDU len %d type %#x, ts_remain %d, next 2: %x.\n", + priv->ts_count, ule_crc, expected_crc, priv->ule_sndu_len, priv->ule_sndu_type, ts_remain, ts_remain > 2 ? *(unsigned short *)from_where : 0); + +#ifdef 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 + + dev->stats.rx_errors++; + dev->stats.rx_crc_errors++; + dev_kfree_skb(priv->ule_skb); + } else { + /* CRC32 verified OK. */ + u8 dest_addr[ETH_ALEN]; + static const u8 bc_addr[ETH_ALEN] = + { [ 0 ... ETH_ALEN-1] = 0xff }; + + /* CRC32 was OK. Remove it from skb. */ + priv->ule_skb->tail -= 4; + priv->ule_skb->len -= 4; + + if (!priv->ule_dbit) { + /* + * 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. + */ + register int drop = 0; + if (priv->rx_mode != RX_MODE_PROMISC) { + if (priv->ule_skb->data[0] & 0x01) { + /* multicast or broadcast */ + if (memcmp(priv->ule_skb->data, bc_addr, ETH_ALEN)) { + /* multicast */ + if (priv->rx_mode == RX_MODE_MULTI) { + int i; + for(i = 0; i < priv->multi_num && memcmp(priv->ule_skb->data, priv->multi_macs[i], ETH_ALEN); i++) + ; + if (i == priv->multi_num) + drop = 1; + } else if (priv->rx_mode != RX_MODE_ALL_MULTI) + drop = 1; /* no broadcast; */ + /* else: all multicast mode: accept all multicast packets */ + } + /* else: broadcast */ + } + else if (memcmp(priv->ule_skb->data, dev->dev_addr, ETH_ALEN)) + drop = 1; + /* else: destination address matches the MAC address of our receiver device */ + } + /* else: promiscuous mode; pass everything up the stack */ + + if (drop) { +#ifdef ULE_DEBUG + dprintk("Dropping SNDU: MAC destination address does not match: dest addr: "MAC_ADDR_PRINTFMT", dev addr: "MAC_ADDR_PRINTFMT"\n", + MAX_ADDR_PRINTFMT_ARGS(priv->ule_skb->data), MAX_ADDR_PRINTFMT_ARGS(dev->dev_addr)); +#endif + dev_kfree_skb(priv->ule_skb); + goto sndu_done; + } + else + { + skb_copy_from_linear_data(priv->ule_skb, + dest_addr, + ETH_ALEN); + skb_pull(priv->ule_skb, ETH_ALEN); + } + } + + /* Handle ULE Extension Headers. */ + if (priv->ule_sndu_type < 1536) { + /* There is an extension header. Handle it accordingly. */ + int l = handle_ule_extensions(priv); + if (l < 0) { + /* Mandatory extension header unknown or TEST SNDU. Drop it. */ + // printk( KERN_WARNING "Dropping SNDU, extension headers.\n" ); + dev_kfree_skb(priv->ule_skb); + goto sndu_done; + } + skb_pull(priv->ule_skb, l); + } + + /* + * Construct/assure correct ethernet header. + * Note: in bridged mode (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 (!priv->ule_bridged) { + skb_push(priv->ule_skb, ETH_HLEN); + ethh = (struct ethhdr *)priv->ule_skb->data; + if (!priv->ule_dbit) { + /* dest_addr buffer is only valid if priv->ule_dbit == 0 */ + memcpy(ethh->h_dest, dest_addr, ETH_ALEN); + memset(ethh->h_source, 0, ETH_ALEN); + } + else /* zeroize source and dest */ + memset( ethh, 0, ETH_ALEN*2 ); + + ethh->h_proto = htons(priv->ule_sndu_type); + } + /* else: skb is in correct state; nothing to do. */ + priv->ule_bridged = 0; + + /* Stuff into kernel's protocol stack. */ + priv->ule_skb->protocol = dvb_net_eth_type_trans(priv->ule_skb, dev); + /* If D-bit is set (i.e. destination MAC address not present), + * receive the packet anyhow. */ + /* if (priv->ule_dbit && skb->pkt_type == PACKET_OTHERHOST) + priv->ule_skb->pkt_type = PACKET_HOST; */ + dev->stats.rx_packets++; + dev->stats.rx_bytes += priv->ule_skb->len; + netif_rx(priv->ule_skb); + } + sndu_done: + /* Prepare for next SNDU. */ + reset_ule(priv); + } + + /* More data in current TS (look at the bytes following the CRC32)? */ + if (ts_remain >= 2 && *((unsigned short *)from_where) != 0xFFFF) { + /* Next ULE SNDU starts right there. */ + new_ts = 0; + priv->ule_skb = NULL; + priv->ule_sndu_type_1 = 0; + priv->ule_sndu_len = 0; + // printk(KERN_WARNING "More data in current TS: [%#x %#x %#x %#x]\n", + // *(from_where + 0), *(from_where + 1), + // *(from_where + 2), *(from_where + 3)); + // printk(KERN_WARNING "ts @ %p, stopped @ %p:\n", ts, from_where + 0); + // hexdump(ts, 188); + } else { + new_ts = 1; + ts += TS_SZ; + priv->ts_count++; + if (priv->ule_skb == NULL) { + priv->need_pusi = 1; + priv->ule_sndu_type_1 = 0; + 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, enum dmx_success success) +{ + struct net_device *dev = feed->priv; + + if (buffer2) + printk(KERN_WARNING "buffer2 not NULL: %p.\n", buffer2); + if (buffer1_len > 32768) + printk(KERN_WARNING "length > 32k: %zu.\n", buffer1_len); + /* printk("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) { + printk("%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))) { + //printk(KERN_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 = (u8 *) 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, + enum dmx_success success) +{ + 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 int 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, + 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) { + printk("%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]; + + dprintk("%s: filter mac=%pM\n", dev->name, mac); + dprintk("%s: filter mask=%pM\n", dev->name, 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; + unsigned char *mac = (unsigned char *) dev->dev_addr; + + dprintk("%s: rx_mode %i\n", __func__, priv->rx_mode); + mutex_lock(&priv->mutex); + if (priv->tsfeed || priv->secfeed || priv->secfilter || priv->multi_secfilter[0]) + printk("%s: BUG %d\n", __func__, __LINE__); + + priv->secfeed=NULL; + priv->secfilter=NULL; + priv->tsfeed = NULL; + + if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { + dprintk("%s: alloc secfeed\n", __func__); + ret=demux->allocate_section_feed(demux, &priv->secfeed, + dvb_net_sec_callback); + if (ret<0) { + printk("%s: could not allocate section feed\n", dev->name); + goto error; + } + + ret = priv->secfeed->set(priv->secfeed, priv->pid, 32768, 1); + + if (ret<0) { + printk("%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) { + dprintk("%s: set secfilter\n", __func__); + 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++) { + dprintk("%s: set multi_secfilter[%d]\n", __func__, 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; + dprintk("%s: set multi_secfilter[0]\n", __func__); + dvb_net_filter_sec_set(dev, &priv->multi_secfilter[0], + mac_allmulti, mask_allmulti); + break; + case RX_MODE_PROMISC: + priv->multi_num=0; + dprintk("%s: set secfilter\n", __func__); + dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_promisc); + break; + } + + dprintk("%s: start filtering\n", __func__); + priv->secfeed->start_filtering(priv->secfeed); + } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { + struct timespec timeout = { 0, 10000000 }; // 10 msec + + /* we have payloads encapsulated in TS */ + dprintk("%s: alloc tsfeed\n", __func__); + ret = demux->allocate_ts_feed(demux, &priv->tsfeed, dvb_net_ts_callback); + if (ret < 0) { + printk("%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_TS_PES_OTHER, /* pes type */ + 32768, /* circular buffer size */ + timeout /* timeout */ + ); + + if (ret < 0) { + printk("%s: could not set ts feed\n", dev->name); + priv->demux->release_ts_feed(priv->demux, priv->tsfeed); + priv->tsfeed = NULL; + goto error; + } + + dprintk("%s: start filtering\n", __func__); + 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; + + dprintk("%s\n", __func__); + mutex_lock(&priv->mutex); + if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { + if (priv->secfeed) { + if (priv->secfeed->is_filtering) { + dprintk("%s: stop secfeed\n", __func__); + priv->secfeed->stop_filtering(priv->secfeed); + } + + if (priv->secfilter) { + dprintk("%s: release secfilter\n", __func__); + priv->secfeed->release_filter(priv->secfeed, + priv->secfilter); + priv->secfilter=NULL; + } + + for (i=0; i<priv->multi_num; i++) { + if (priv->multi_secfilter[i]) { + dprintk("%s: release multi_filter[%d]\n", + __func__, 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 + printk("%s: no feed to stop\n", dev->name); + } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { + if (priv->tsfeed) { + if (priv->tsfeed->is_filtering) { + dprintk("%s: stop tsfeed\n", __func__); + priv->tsfeed->stop_filtering(priv->tsfeed); + } + priv->demux->release_ts_feed(priv->demux, priv->tsfeed); + priv->tsfeed = NULL; + } + else + printk("%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) { + dprintk("%s: promiscuous mode\n", dev->name); + priv->rx_mode = RX_MODE_PROMISC; + } else if ((dev->flags & IFF_ALLMULTI)) { + dprintk("%s: allmulti mode\n", dev->name); + priv->rx_mode = RX_MODE_ALL_MULTI; + } else if (!netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + + dprintk("%s: set_mc_list, %d entries\n", + dev->name, 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; + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + 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, + .rebuild = eth_rebuild_header, +}; + + +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_change_mtu = eth_change_mtu, + .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->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", 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; + memcpy(net->dev_addr, dvbnet->dvbdev->adapter->proposed_mac, 6); + + 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; + } + printk("dvb_net: 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_sync(&priv->set_multicast_list_wq); + flush_work_sync(&priv->restart_net_feed_wq); + printk("dvb_net: 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; + + if (((file->f_flags&O_ACCMODE)==O_RDONLY)) + return -EPERM; + + switch (cmd) { + case NET_ADD_IF: + { + struct dvb_net_if *dvbnetif = parg; + int result; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!try_module_get(dvbdev->adapter->module)) + return -EPERM; + + result=dvb_net_add_if(dvbnet, dvbnetif->pid, dvbnetif->feedtype); + if (result<0) { + module_put(dvbdev->adapter->module); + return result; + } + 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; + + if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX || + !dvbnet->state[dvbnetif->if_num]) + return -EINVAL; + + netdev = dvbnet->device[dvbnetif->if_num]; + + priv_data = netdev_priv(netdev); + dvbnetif->pid=priv_data->pid; + dvbnetif->feedtype=priv_data->feedtype; + break; + } + case NET_REMOVE_IF: + { + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if ((unsigned long) parg >= DVB_NET_DEVICES_MAX) + return -EINVAL; + ret = dvb_net_remove_if(dvbnet, (unsigned long) parg); + if (!ret) + module_put(dvbdev->adapter->module); + return ret; + } + + /* binary compatibility cruft */ + case __NET_ADD_IF_OLD: + { + struct __dvb_net_if_old *dvbnetif = parg; + int result; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!try_module_get(dvbdev->adapter->module)) + return -EPERM; + + result=dvb_net_add_if(dvbnet, dvbnetif->pid, DVB_NET_FEEDTYPE_MPE); + if (result<0) { + module_put(dvbdev->adapter->module); + return result; + } + 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; + + if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX || + !dvbnet->state[dvbnetif->if_num]) + return -EINVAL; + + netdev = dvbnet->device[dvbnetif->if_num]; + + priv_data = netdev_priv(netdev); + dvbnetif->pid=priv_data->pid; + break; + } + default: + return -ENOTTY; + } + return 0; +} + +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 dvb_net_close(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_net *dvbnet = dvbdev->priv; + + dvb_generic_release(inode, file); + + if(dvbdev->users == 1 && dvbnet->exit == 1) { + fops_put(file->f_op); + file->f_op = NULL; + wake_up(&dvbdev->wait_queue); + } + return 0; +} + + +static const struct file_operations dvb_net_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dvb_net_ioctl, + .open = dvb_generic_open, + .release = dvb_net_close, + .llseek = noop_llseek, +}; + +static struct dvb_device dvbdev_net = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_net_fops, +}; + + +void dvb_net_release (struct dvb_net *dvbnet) +{ + int i; + + dvbnet->exit = 1; + 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; + + 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); +} +EXPORT_SYMBOL(dvb_net_init); diff --git a/drivers/media/dvb-core/dvb_net.h b/drivers/media/dvb-core/dvb_net.h new file mode 100644 index 000000000000..1e53acd50cf4 --- /dev/null +++ b/drivers/media/dvb-core/dvb_net.h @@ -0,0 +1,66 @@ +/* + * dvb_net.h + * + * Copyright (C) 2001 Ralph Metzler for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DVB_NET_H_ +#define _DVB_NET_H_ + +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/inetdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "dvbdev.h" + +#define DVB_NET_DEVICES_MAX 10 + +#ifdef CONFIG_DVB_NET + +struct dvb_net { + struct dvb_device *dvbdev; + struct net_device *device[DVB_NET_DEVICES_MAX]; + int state[DVB_NET_DEVICES_MAX]; + unsigned int exit:1; + struct dmx_demux *demux; +}; + +void dvb_net_release(struct dvb_net *); +int dvb_net_init(struct dvb_adapter *, struct dvb_net *, struct dmx_demux *); + +#else + +struct dvb_net { + struct dvb_device *dvbdev; +}; + +static inline void dvb_net_release(struct dvb_net *dvbnet) +{ +} + +static inline int dvb_net_init(struct dvb_adapter *adap, + struct dvb_net *dvbnet, struct dmx_demux *dmx) +{ + return 0; +} + +#endif /* ifdef CONFIG_DVB_NET */ + +#endif diff --git a/drivers/media/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb-core/dvb_ringbuffer.c new file mode 100644 index 000000000000..a5712cd7c65f --- /dev/null +++ b/drivers/media/dvb-core/dvb_ringbuffer.c @@ -0,0 +1,299 @@ +/* + * + * 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. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <asm/uaccess.h> + +#include "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) +{ + return (rbuf->pread==rbuf->pwrite); +} + + + +ssize_t dvb_ringbuffer_free(struct dvb_ringbuffer *rbuf) +{ + ssize_t free; + + free = 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; + + avail = rbuf->pwrite - rbuf->pread; + if (avail < 0) + avail += rbuf->size; + return avail; +} + + + +void dvb_ringbuffer_flush(struct dvb_ringbuffer *rbuf) +{ + rbuf->pread = rbuf->pwrite; + rbuf->error = 0; +} +EXPORT_SYMBOL(dvb_ringbuffer_flush); + +void dvb_ringbuffer_reset(struct dvb_ringbuffer *rbuf) +{ + rbuf->pread = 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; + rbuf->pread = 0; + } + if (copy_to_user(buf, rbuf->data+rbuf->pread, todo)) + return -EFAULT; + + 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; + rbuf->pread = 0; + } + memcpy(buf, rbuf->data+rbuf->pread, todo); + + 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; + rbuf->pwrite = 0; + } + memcpy(rbuf->data+rbuf->pwrite, buf, todo); + 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); diff --git a/drivers/media/dvb-core/dvb_ringbuffer.h b/drivers/media/dvb-core/dvb_ringbuffer.h new file mode 100644 index 000000000000..41f04dae69b6 --- /dev/null +++ b/drivers/media/dvb-core/dvb_ringbuffer.h @@ -0,0 +1,186 @@ +/* + * + * dvb_ringbuffer.h: 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. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DVB_RINGBUFFER_H_ +#define _DVB_RINGBUFFER_H_ + +#include <linux/spinlock.h> +#include <linux/wait.h> + +struct dvb_ringbuffer { + u8 *data; + ssize_t size; + ssize_t pread; + ssize_t pwrite; + int error; + + wait_queue_head_t queue; + spinlock_t lock; +}; + +#define DVB_RINGBUFFER_PKTHDRSIZE 3 + + +/* +** Notes: +** ------ +** (1) For performance reasons read and write routines don't check buffer sizes +** and/or number of bytes free/available. This has to be done before these +** routines are called. For example: +** +** *** write <buflen> bytes *** +** free = dvb_ringbuffer_free(rbuf); +** if (free >= buflen) +** count = dvb_ringbuffer_write(rbuf, buffer, buflen); +** else +** ... +** +** *** read min. 1000, max. <bufsize> bytes *** +** avail = dvb_ringbuffer_avail(rbuf); +** if (avail >= 1000) +** count = dvb_ringbuffer_read(rbuf, buffer, min(avail, bufsize)); +** else +** ... +** +** (2) If there is exactly one reader and one writer, there is no need +** to lock read or write operations. +** Two or more readers must be locked against each other. +** Flushing the buffer counts as a read operation. +** Resetting the buffer counts as a read and write operation. +** Two or more writers must be locked against each other. +*/ + +/* initialize ring buffer, lock and queue */ +extern void dvb_ringbuffer_init(struct dvb_ringbuffer *rbuf, void *data, size_t len); + +/* test whether buffer is empty */ +extern int dvb_ringbuffer_empty(struct dvb_ringbuffer *rbuf); + +/* return the number of free bytes in the buffer */ +extern ssize_t dvb_ringbuffer_free(struct dvb_ringbuffer *rbuf); + +/* return the number of bytes waiting in the buffer */ +extern ssize_t dvb_ringbuffer_avail(struct dvb_ringbuffer *rbuf); + + +/* +** Reset the read and write pointers to zero and flush the buffer +** This counts as a read and write operation +*/ +extern void dvb_ringbuffer_reset(struct dvb_ringbuffer *rbuf); + + +/* read routines & macros */ +/* ---------------------- */ +/* flush buffer */ +extern void dvb_ringbuffer_flush(struct dvb_ringbuffer *rbuf); + +/* flush buffer protected by spinlock and wake-up waiting task(s) */ +extern void dvb_ringbuffer_flush_spinlock_wakeup(struct dvb_ringbuffer *rbuf); + +/* peek at byte <offs> in the buffer */ +#define DVB_RINGBUFFER_PEEK(rbuf,offs) \ + (rbuf)->data[((rbuf)->pread+(offs))%(rbuf)->size] + +/* advance read ptr by <num> bytes */ +#define DVB_RINGBUFFER_SKIP(rbuf,num) \ + (rbuf)->pread=((rbuf)->pread+(num))%(rbuf)->size + +/* +** read <len> bytes from ring buffer into <buf> +** <usermem> specifies whether <buf> resides in user space +** returns number of bytes transferred or -EFAULT +*/ +extern ssize_t dvb_ringbuffer_read_user(struct dvb_ringbuffer *rbuf, + u8 __user *buf, size_t len); +extern void dvb_ringbuffer_read(struct dvb_ringbuffer *rbuf, + u8 *buf, size_t len); + + +/* write routines & macros */ +/* ----------------------- */ +/* write single byte to ring buffer */ +#define DVB_RINGBUFFER_WRITE_BYTE(rbuf,byte) \ + { (rbuf)->data[(rbuf)->pwrite]=(byte); \ + (rbuf)->pwrite=((rbuf)->pwrite+1)%(rbuf)->size; } +/* +** write <len> bytes to ring buffer +** <usermem> specifies whether <buf> resides in user space +** returns number of bytes transferred or -EFAULT +*/ +extern ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, + size_t len); + + +/** + * Write a packet into the ringbuffer. + * + * <rbuf> Ringbuffer to write to. + * <buf> Buffer to write. + * <len> Length of buffer (currently limited to 65535 bytes max). + * returns Number of bytes written, or -EFAULT, -ENOMEM, -EVINAL. + */ +extern ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8* buf, + size_t len); + +/** + * Read from a packet in the ringbuffer. Note: unlike dvb_ringbuffer_read(), this + * does NOT update the read pointer in the ringbuffer. You must use + * dvb_ringbuffer_pkt_dispose() to mark a packet as no longer required. + * + * <rbuf> Ringbuffer concerned. + * <idx> Packet index as returned by dvb_ringbuffer_pkt_next(). + * <offset> Offset into packet to read from. + * <buf> Destination buffer for data. + * <len> Size of destination buffer. + * <usermem> Set to 1 if <buf> is in userspace. + * returns Number of bytes read, or -EFAULT. + */ +extern ssize_t dvb_ringbuffer_pkt_read_user(struct dvb_ringbuffer *rbuf, size_t idx, + int offset, u8 __user *buf, size_t len); +extern ssize_t dvb_ringbuffer_pkt_read(struct dvb_ringbuffer *rbuf, size_t idx, + int offset, u8 *buf, size_t len); + +/** + * Dispose of a packet in the ring buffer. + * + * <rbuf> Ring buffer concerned. + * <idx> Packet index as returned by dvb_ringbuffer_pkt_next(). + */ +extern void dvb_ringbuffer_pkt_dispose(struct dvb_ringbuffer *rbuf, size_t idx); + +/** + * Get the index of the next packet in a ringbuffer. + * + * <rbuf> Ringbuffer concerned. + * <idx> Previous packet index, or -1 to return the first packet index. + * <pktlen> On success, will be updated to contain the length of the packet in bytes. + * returns Packet index (if >=0), or -1 if no packets available. + */ +extern ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* pktlen); + + +#endif /* _DVB_RINGBUFFER_H_ */ diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbdev.c new file mode 100644 index 000000000000..39eab73b01ae --- /dev/null +++ b/drivers/media/dvb-core/dvbdev.c @@ -0,0 +1,507 @@ +/* + * dvbdev.c + * + * Copyright (C) 2000 Ralph Metzler <ralph@convergence.de> + * & Marcus Metzler <marcus@convergence.de> + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/kernel.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 "dvbdev.h" + +static DEFINE_MUTEX(dvbdev_mutex); +static int dvbdev_debug; + +module_param(dvbdev_debug, int, 0644); +MODULE_PARM_DESC(dvbdev_debug, "Turn on/off device debugging (default:off)."); + +#define dprintk if (dvbdev_debug) printk + +static LIST_HEAD(dvb_adapter_list); +static DEFINE_MUTEX(dvbdev_register_lock); + +static const char * const dnames[] = { + "video", "audio", "sec", "frontend", "demux", "dvr", "ca", + "net", "osd" +}; + +#ifdef CONFIG_DVB_DYNAMIC_MINORS +#define MAX_DVB_MINORS 256 +#define DVB_MAX_IDS MAX_DVB_MINORS +#else +#define DVB_MAX_IDS 4 +#define nums2minor(num,type,id) ((num << 6) | (id << 4) | 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 *old_fops; + + file->private_data = dvbdev; + old_fops = file->f_op; + file->f_op = fops_get(dvbdev->fops); + if (file->f_op == NULL) { + file->f_op = old_fops; + goto fail; + } + if(file->f_op->open) + err = file->f_op->open(inode,file); + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + up_read(&minor_rwsem); + mutex_unlock(&dvbdev_mutex); + 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++; + 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; +} + + +int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, + const struct dvb_device *template, void *priv, int type) +{ + struct dvb_device *dvbdev; + struct file_operations *dvbdevfops; + struct device *clsdev; + int minor; + int id; + + mutex_lock(&dvbdev_register_lock); + + if ((id = dvbdev_get_free_id (adap, type)) < 0){ + mutex_unlock(&dvbdev_register_lock); + *pdvbdev = NULL; + printk(KERN_ERR "%s: couldn't find free device id\n", __func__); + return -ENFILE; + } + + *pdvbdev = dvbdev = kmalloc(sizeof(struct dvb_device), GFP_KERNEL); + + if (!dvbdev){ + mutex_unlock(&dvbdev_register_lock); + return -ENOMEM; + } + + dvbdevfops = kzalloc(sizeof(struct file_operations), GFP_KERNEL); + + if (!dvbdevfops){ + kfree (dvbdev); + mutex_unlock(&dvbdev_register_lock); + return -ENOMEM; + } + + memcpy(dvbdev, template, sizeof(struct dvb_device)); + dvbdev->type = type; + dvbdev->id = id; + dvbdev->adapter = adap; + dvbdev->priv = priv; + dvbdev->fops = dvbdevfops; + init_waitqueue_head (&dvbdev->wait_queue); + + memcpy(dvbdevfops, template->fops, sizeof(struct file_operations)); + 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) { + kfree(dvbdevfops); + 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] = dvbdev; + up_write(&minor_rwsem); + + mutex_unlock(&dvbdev_register_lock); + + 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)) { + printk(KERN_ERR "%s: failed to create device dvb%d.%s%d (%ld)\n", + __func__, adap->num, dnames[type], id, PTR_ERR(clsdev)); + return PTR_ERR(clsdev); + } + + dprintk(KERN_DEBUG "DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n", + adap->num, dnames[type], id, minor, minor); + + return 0; +} +EXPORT_SYMBOL(dvb_register_device); + + +void dvb_unregister_device(struct dvb_device *dvbdev) +{ + if (!dvbdev) + return; + + down_write(&minor_rwsem); + dvb_minors[dvbdev->minor] = NULL; + up_write(&minor_rwsem); + + device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor)); + + list_del (&dvbdev->list_head); + kfree (dvbdev->fops); + kfree (dvbdev); +} +EXPORT_SYMBOL(dvb_unregister_device); + +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); + + printk(KERN_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); + + 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 dependecy + 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 */ + mutex_lock(&dvbdev_mutex); + if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + mutex_unlock(&dvbdev_mutex); + + 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; +} + +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) { + printk(KERN_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) { + printk(KERN_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) +{ + class_destroy(dvb_class); + cdev_del(&dvb_device_cdev); + unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS); +} + +subsys_initcall(init_dvbdev); +module_exit(exit_dvbdev); + +MODULE_DESCRIPTION("DVB Core Driver"); +MODULE_AUTHOR("Marcus Metzler, Ralph Metzler, Holger Waechtler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-core/dvbdev.h b/drivers/media/dvb-core/dvbdev.h new file mode 100644 index 000000000000..93a9470d3f0c --- /dev/null +++ b/drivers/media/dvb-core/dvbdev.h @@ -0,0 +1,146 @@ +/* + * dvbdev.h + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Lesser Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DVBDEV_H_ +#define _DVBDEV_H_ + +#include <linux/types.h> +#include <linux/poll.h> +#include <linux/fs.h> +#include <linux/list.h> + +#define DVB_MAJOR 212 + +#if defined(CONFIG_DVB_MAX_ADAPTERS) && CONFIG_DVB_MAX_ADAPTERS > 0 + #define DVB_MAX_ADAPTERS CONFIG_DVB_MAX_ADAPTERS +#else + #define DVB_MAX_ADAPTERS 8 +#endif + +#define DVB_UNSET (-1) + +#define DVB_DEVICE_VIDEO 0 +#define DVB_DEVICE_AUDIO 1 +#define DVB_DEVICE_SEC 2 +#define DVB_DEVICE_FRONTEND 3 +#define DVB_DEVICE_DEMUX 4 +#define DVB_DEVICE_DVR 5 +#define DVB_DEVICE_CA 6 +#define DVB_DEVICE_NET 7 +#define DVB_DEVICE_OSD 8 + +#define DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr) \ + static short adapter_nr[] = \ + {[0 ... (DVB_MAX_ADAPTERS - 1)] = DVB_UNSET }; \ + module_param_array(adapter_nr, short, NULL, 0444); \ + MODULE_PARM_DESC(adapter_nr, "DVB adapter numbers") + +struct dvb_frontend; + +struct dvb_adapter { + int num; + struct list_head list_head; + struct list_head device_list; + const char *name; + u8 proposed_mac [6]; + void* priv; + + struct device *device; + + struct module *module; + + int mfe_shared; /* indicates mutually exclusive frontends */ + struct dvb_device *mfe_dvbdev; /* frontend device in use */ + struct mutex mfe_lock; /* access lock for thread creation */ +}; + + +struct dvb_device { + struct list_head list_head; + const struct file_operations *fops; + struct dvb_adapter *adapter; + int type; + int minor; + u32 id; + + /* in theory, 'users' can vanish now, + but I don't want to change too much now... */ + int readers; + int writers; + int users; + + wait_queue_head_t wait_queue; + /* don't really need those !? -- FIXME: use video_usercopy */ + int (*kernel_ioctl)(struct file *file, unsigned int cmd, void *arg); + + void *priv; +}; + + +extern int dvb_register_adapter(struct dvb_adapter *adap, const char *name, + struct module *module, struct device *device, + short *adapter_nums); +extern int dvb_unregister_adapter (struct dvb_adapter *adap); + +extern int dvb_register_device (struct dvb_adapter *adap, + struct dvb_device **pdvbdev, + const struct dvb_device *template, + void *priv, + int type); + +extern void dvb_unregister_device (struct dvb_device *dvbdev); + +extern int dvb_generic_open (struct inode *inode, struct file *file); +extern int dvb_generic_release (struct inode *inode, struct file *file); +extern long dvb_generic_ioctl (struct file *file, + unsigned int cmd, unsigned long arg); + +/* we don't mess with video_usercopy() any more, +we simply define out own dvb_usercopy(), which will hopefully become +generic_usercopy() someday... */ + +extern int dvb_usercopy(struct file *file, unsigned int cmd, unsigned long arg, + int (*func)(struct file *file, unsigned int cmd, void *arg)); + +/** generic DVB attach function. */ +#ifdef CONFIG_MEDIA_ATTACH +#define dvb_attach(FUNCTION, ARGS...) ({ \ + void *__r = NULL; \ + typeof(&FUNCTION) __a = symbol_request(FUNCTION); \ + if (__a) { \ + __r = (void *) __a(ARGS); \ + if (__r == NULL) \ + symbol_put(FUNCTION); \ + } else { \ + printk(KERN_ERR "DVB: Unable to find symbol "#FUNCTION"()\n"); \ + } \ + __r; \ +}) + +#else +#define dvb_attach(FUNCTION, ARGS...) ({ \ + FUNCTION(ARGS); \ +}) + +#endif + +#endif /* #ifndef _DVBDEV_H_ */ |