root/drivers/media/dvb-core/dvb_ca_en50221.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * dvb_ca.c: generic DVB functions for EN50221 CAM interfaces
 *
 * Copyright (C) 2004 Andrew de Quincey
 *
 * Parts of this file were based on sources as follows:
 *
 * Copyright (C) 2003 Ralph Metzler <rjkm@metzlerbros.de>
 *
 * based on code:
 *
 * Copyright (C) 1999-2002 Ralph  Metzler
 *                       & Marcus Metzler for convergence integrated media GmbH
 */

#define pr_fmt(fmt) "dvb_ca_en50221: " fmt

#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/nospec.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/sched/signal.h>
#include <linux/kthread.h>

#include <media/dvb_ca_en50221.h>
#include <media/dvb_ringbuffer.h>

static int dvb_ca_en50221_debug;

module_param_named(cam_debug, dvb_ca_en50221_debug, int, 0644);
MODULE_PARM_DESC(cam_debug, "enable verbose debug messages");

#define dprintk(fmt, arg...) do {                                       \
        if (dvb_ca_en50221_debug)                                       \
                printk(KERN_DEBUG pr_fmt("%s: " fmt), __func__, ##arg);\
} while (0)

#define INIT_TIMEOUT_SECS 10

#define HOST_LINK_BUF_SIZE 0x200

#define RX_BUFFER_SIZE 65535

#define MAX_RX_PACKETS_PER_ITERATION 10

#define CTRLIF_DATA      0
#define CTRLIF_COMMAND   1
#define CTRLIF_STATUS    1
#define CTRLIF_SIZE_LOW  2
#define CTRLIF_SIZE_HIGH 3

#define CMDREG_HC        1      /* Host control */
#define CMDREG_SW        2      /* Size write */
#define CMDREG_SR        4      /* Size read */
#define CMDREG_RS        8      /* Reset interface */
#define CMDREG_FRIE   0x40      /* Enable FR interrupt */
#define CMDREG_DAIE   0x80      /* Enable DA interrupt */
#define IRQEN (CMDREG_DAIE)

#define STATUSREG_RE     1      /* read error */
#define STATUSREG_WE     2      /* write error */
#define STATUSREG_FR  0x40      /* module free */
#define STATUSREG_DA  0x80      /* data available */

#define DVB_CA_SLOTSTATE_NONE           0
#define DVB_CA_SLOTSTATE_UNINITIALISED  1
#define DVB_CA_SLOTSTATE_RUNNING        2
#define DVB_CA_SLOTSTATE_INVALID        3
#define DVB_CA_SLOTSTATE_WAITREADY      4
#define DVB_CA_SLOTSTATE_VALIDATE       5
#define DVB_CA_SLOTSTATE_WAITFR         6
#define DVB_CA_SLOTSTATE_LINKINIT       7

/* Information on a CA slot */
struct dvb_ca_slot {
        /* current state of the CAM */
        int slot_state;

        /* mutex used for serializing access to one CI slot */
        struct mutex slot_lock;

        /* Number of CAMCHANGES that have occurred since last processing */
        atomic_t camchange_count;

        /* Type of last CAMCHANGE */
        int camchange_type;

        /* base address of CAM config */
        u32 config_base;

        /* value to write into Config Control register */
        u8 config_option;

        /* if 1, the CAM supports DA IRQs */
        u8 da_irq_supported:1;

        /* size of the buffer to use when talking to the CAM */
        int link_buf_size;

        /* buffer for incoming packets */
        struct dvb_ringbuffer rx_buffer;

        /* timer used during various states of the slot */
        unsigned long timeout;
};

/* Private CA-interface information */
struct dvb_ca_private {
        struct kref refcount;

        /* pointer back to the public data structure */
        struct dvb_ca_en50221 *pub;

        /* the DVB device */
        struct dvb_device *dvbdev;

        /* Flags describing the interface (DVB_CA_FLAG_*) */
        u32 flags;

        /* number of slots supported by this CA interface */
        unsigned int slot_count;

        /* information on each slot */
        struct dvb_ca_slot *slot_info;

        /* wait queues for read() and write() operations */
        wait_queue_head_t wait_queue;

        /* PID of the monitoring thread */
        struct task_struct *thread;

        /* Flag indicating if the CA device is open */
        unsigned int open:1;

        /* Flag indicating the thread should wake up now */
        unsigned int wakeup:1;

        /* Delay the main thread should use */
        unsigned long delay;

        /*
         * Slot to start looking for data to read from in the next user-space
         * read operation
         */
        int next_read_slot;

        /* mutex serializing ioctls */
        struct mutex ioctl_mutex;

        /* A mutex used when a device is disconnected */
        struct mutex remove_mutex;

        /* Whether the device is disconnected */
        int exit;
};

static void dvb_ca_private_free(struct dvb_ca_private *ca)
{
        unsigned int i;

        dvb_device_put(ca->dvbdev);
        for (i = 0; i < ca->slot_count; i++)
                vfree(ca->slot_info[i].rx_buffer.data);

        kfree(ca->slot_info);
        kfree(ca);
}

static void dvb_ca_private_release(struct kref *ref)
{
        struct dvb_ca_private *ca;

        ca = container_of(ref, struct dvb_ca_private, refcount);
        dvb_ca_private_free(ca);
}

static void dvb_ca_private_get(struct dvb_ca_private *ca)
{
        kref_get(&ca->refcount);
}

static void dvb_ca_private_put(struct dvb_ca_private *ca)
{
        kref_put(&ca->refcount, dvb_ca_private_release);
}

static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca);
static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot,
                                    u8 *ebuf, int ecount);
static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot,
                                     u8 *ebuf, int ecount, int size_write_flag);

/**
 * findstr - Safely find needle in haystack.
 *
 * @haystack: Buffer to look in.
 * @hlen: Number of bytes in haystack.
 * @needle: Buffer to find.
 * @nlen: Number of bytes in needle.
 * return: Pointer into haystack needle was found at, or NULL if not found.
 */
static char *findstr(char *haystack, int hlen, char *needle, int nlen)
{
        int i;

        if (hlen < nlen)
                return NULL;

        for (i = 0; i <= hlen - nlen; i++) {
                if (!strncmp(haystack + i, needle, nlen))
                        return haystack + i;
        }

        return NULL;
}

/* ************************************************************************** */
/* EN50221 physical interface functions */

/*
 * dvb_ca_en50221_check_camstatus - Check CAM status.
 */
static int dvb_ca_en50221_check_camstatus(struct dvb_ca_private *ca, int slot)
{
        struct dvb_ca_slot *sl = &ca->slot_info[slot];
        int slot_status;
        int cam_present_now;
        int cam_changed;

        /* IRQ mode */
        if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)
                return (atomic_read(&sl->camchange_count) != 0);

        /* poll mode */
        slot_status = ca->pub->poll_slot_status(ca->pub, slot, ca->open);

        cam_present_now = (slot_status & DVB_CA_EN50221_POLL_CAM_PRESENT) ? 1 : 0;
        cam_changed = (slot_status & DVB_CA_EN50221_POLL_CAM_CHANGED) ? 1 : 0;
        if (!cam_changed) {
                int cam_present_old = (sl->slot_state != DVB_CA_SLOTSTATE_NONE);

                cam_changed = (cam_present_now != cam_present_old);
        }

        if (cam_changed) {
                if (!cam_present_now)
                        sl->camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED;
                else
                        sl->camchange_type = DVB_CA_EN50221_CAMCHANGE_INSERTED;
                atomic_set(&sl->camchange_count, 1);
        } else {
                if ((sl->slot_state == DVB_CA_SLOTSTATE_WAITREADY) &&
                    (slot_status & DVB_CA_EN50221_POLL_CAM_READY)) {
                        /* move to validate state if reset is completed */
                        sl->slot_state = DVB_CA_SLOTSTATE_VALIDATE;
                }
        }

        return cam_changed;
}

/**
 * dvb_ca_en50221_wait_if_status - Wait for flags to become set on the STATUS
 *       register on a CAM interface, checking for errors and timeout.
 *
 * @ca: CA instance.
 * @slot: Slot on interface.
 * @waitfor: Flags to wait for.
 * @timeout_hz: Timeout in milliseconds.
 *
 * return: 0 on success, nonzero on error.
 */
static int dvb_ca_en50221_wait_if_status(struct dvb_ca_private *ca, int slot,
                                         u8 waitfor, int timeout_hz)
{
        unsigned long timeout;
        unsigned long start;

        dprintk("%s\n", __func__);

        /* loop until timeout elapsed */
        start = jiffies;
        timeout = jiffies + timeout_hz;
        while (1) {
                int res;

                /* read the status and check for error */
                res = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS);
                if (res < 0)
                        return -EIO;

                /* if we got the flags, it was successful! */
                if (res & waitfor) {
                        dprintk("%s succeeded timeout:%lu\n",
                                __func__, jiffies - start);
                        return 0;
                }

                /* check for timeout */
                if (time_after(jiffies, timeout))
                        break;

                /* wait for a bit */
                usleep_range(1000, 1100);
        }

        dprintk("%s failed timeout:%lu\n", __func__, jiffies - start);

        /* if we get here, we've timed out */
        return -ETIMEDOUT;
}

/**
 * dvb_ca_en50221_link_init - Initialise the link layer connection to a CAM.
 *
 * @ca: CA instance.
 * @slot: Slot id.
 *
 * return: 0 on success, nonzero on failure.
 */
static int dvb_ca_en50221_link_init(struct dvb_ca_private *ca, int slot)
{
        struct dvb_ca_slot *sl = &ca->slot_info[slot];
        int ret;
        int buf_size;
        u8 buf[2];

        dprintk("%s\n", __func__);

        /* we'll be determining these during this function */
        sl->da_irq_supported = 0;

        /*
         * set the host link buffer size temporarily. it will be overwritten
         * with the real negotiated size later.
         */
        sl->link_buf_size = 2;

        /* read the buffer size from the CAM */
        ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND,
                                         IRQEN | CMDREG_SR);
        if (ret)
                return ret;
        ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_DA, HZ);
        if (ret)
                return ret;
        ret = dvb_ca_en50221_read_data(ca, slot, buf, 2);
        if (ret != 2)
                return -EIO;
        ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN);
        if (ret)
                return ret;

        /*
         * store it, and choose the minimum of our buffer and the CAM's buffer
         * size
         */
        buf_size = (buf[0] << 8) | buf[1];
        if (buf_size > HOST_LINK_BUF_SIZE)
                buf_size = HOST_LINK_BUF_SIZE;
        sl->link_buf_size = buf_size;
        buf[0] = buf_size >> 8;
        buf[1] = buf_size & 0xff;
        dprintk("Chosen link buffer size of %i\n", buf_size);

        /* write the buffer size to the CAM */
        ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND,
                                         IRQEN | CMDREG_SW);
        if (ret)
                return ret;
        ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_FR, HZ / 10);
        if (ret)
                return ret;
        ret = dvb_ca_en50221_write_data(ca, slot, buf, 2, CMDREG_SW);
        if (ret != 2)
                return -EIO;
        ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN);
        if (ret)
                return ret;

        /* success */
        return 0;
}

/**
 * dvb_ca_en50221_read_tuple - Read a tuple from attribute memory.
 *
 * @ca: CA instance.
 * @slot: Slot id.
 * @address: Address to read from. Updated.
 * @tuple_type: Tuple id byte. Updated.
 * @tuple_length: Tuple length. Updated.
 * @tuple: Dest buffer for tuple (must be 256 bytes). Updated.
 *
 * return: 0 on success, nonzero on error.
 */
static int dvb_ca_en50221_read_tuple(struct dvb_ca_private *ca, int slot,
                                     int *address, int *tuple_type,
                                     int *tuple_length, u8 *tuple)
{
        int i;
        int _tuple_type;
        int _tuple_length;
        int _address = *address;

        /* grab the next tuple length and type */
        _tuple_type = ca->pub->read_attribute_mem(ca->pub, slot, _address);
        if (_tuple_type < 0)
                return _tuple_type;
        if (_tuple_type == 0xff) {
                dprintk("END OF CHAIN TUPLE type:0x%x\n", _tuple_type);
                *address += 2;
                *tuple_type = _tuple_type;
                *tuple_length = 0;
                return 0;
        }
        _tuple_length = ca->pub->read_attribute_mem(ca->pub, slot,
                                                    _address + 2);
        if (_tuple_length < 0)
                return _tuple_length;
        _address += 4;

        dprintk("TUPLE type:0x%x length:%i\n", _tuple_type, _tuple_length);

        /* read in the whole tuple */
        for (i = 0; i < _tuple_length; i++) {
                tuple[i] = ca->pub->read_attribute_mem(ca->pub, slot,
                                                       _address + (i * 2));
                dprintk("  0x%02x: 0x%02x %c\n",
                        i, tuple[i] & 0xff,
                        ((tuple[i] > 31) && (tuple[i] < 127)) ? tuple[i] : '.');
        }
        _address += (_tuple_length * 2);

        /* success */
        *tuple_type = _tuple_type;
        *tuple_length = _tuple_length;
        *address = _address;
        return 0;
}

/**
 * dvb_ca_en50221_parse_attributes - Parse attribute memory of a CAM module,
 *      extracting Config register, and checking it is a DVB CAM module.
 *
 * @ca: CA instance.
 * @slot: Slot id.
 *
 * return: 0 on success, <0 on failure.
 */
static int dvb_ca_en50221_parse_attributes(struct dvb_ca_private *ca, int slot)
{
        struct dvb_ca_slot *sl;
        int address = 0;
        int tuple_length;
        int tuple_type;
        u8 tuple[257];
        char *dvb_str;
        int rasz;
        int status;
        int got_cftableentry = 0;
        int end_chain = 0;
        int i;
        u16 manfid = 0;
        u16 devid = 0;

        /* CISTPL_DEVICE_0A */
        status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type,
                                           &tuple_length, tuple);
        if (status < 0)
                return status;
        if (tuple_type != 0x1D)
                return -EINVAL;

        /* CISTPL_DEVICE_0C */
        status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type,
                                           &tuple_length, tuple);
        if (status < 0)
                return status;
        if (tuple_type != 0x1C)
                return -EINVAL;

        /* CISTPL_VERS_1 */
        status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type,
                                           &tuple_length, tuple);
        if (status < 0)
                return status;
        if (tuple_type != 0x15)
                return -EINVAL;

        /* CISTPL_MANFID */
        status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type,
                                           &tuple_length, tuple);
        if (status < 0)
                return status;
        if (tuple_type != 0x20)
                return -EINVAL;
        if (tuple_length != 4)
                return -EINVAL;
        manfid = (tuple[1] << 8) | tuple[0];
        devid = (tuple[3] << 8) | tuple[2];

        /* CISTPL_CONFIG */
        status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tuple_type,
                                           &tuple_length, tuple);
        if (status < 0)
                return status;
        if (tuple_type != 0x1A)
                return -EINVAL;
        if (tuple_length < 3)
                return -EINVAL;

        /* extract the configbase */
        rasz = tuple[0] & 3;
        if (tuple_length < (3 + rasz + 14))
                return -EINVAL;
        sl = &ca->slot_info[slot];
        sl->config_base = 0;
        for (i = 0; i < rasz + 1; i++)
                sl->config_base |= (tuple[2 + i] << (8 * i));

        /* check it contains the correct DVB string */
        dvb_str = findstr((char *)tuple, tuple_length, "DVB_CI_V", 8);
        if (!dvb_str)
                return -EINVAL;
        if (tuple_length < ((dvb_str - (char *)tuple) + 12))
                return -EINVAL;

        /* is it a version we support? */
        if (strncmp(dvb_str + 8, "1.00", 4)) {
                pr_err("dvb_ca adapter %d: Unsupported DVB CAM module version %c%c%c%c\n",
                       ca->dvbdev->adapter->num, dvb_str[8], dvb_str[9],
                       dvb_str[10], dvb_str[11]);
                return -EINVAL;
        }

        /* process the CFTABLE_ENTRY tuples, and any after those */
        while ((!end_chain) && (address < 0x1000)) {
                status = dvb_ca_en50221_read_tuple(ca, slot, &address,
                                                   &tuple_type, &tuple_length,
                                                   tuple);
                if (status < 0)
                        return status;
                switch (tuple_type) {
                case 0x1B:      /* CISTPL_CFTABLE_ENTRY */
                        if (tuple_length < (2 + 11 + 17))
                                break;

                        /* if we've already parsed one, just use it */
                        if (got_cftableentry)
                                break;

                        /* get the config option */
                        sl->config_option = tuple[0] & 0x3f;

                        /* OK, check it contains the correct strings */
                        if (!findstr((char *)tuple, tuple_length,
                                     "DVB_HOST", 8) ||
                            !findstr((char *)tuple, tuple_length,
                                     "DVB_CI_MODULE", 13))
                                break;

                        got_cftableentry = 1;
                        break;

                case 0x14:      /* CISTPL_NO_LINK */
                        break;

                case 0xFF:      /* CISTPL_END */
                        end_chain = 1;
                        break;

                default:        /* Unknown tuple type - just skip this tuple */
                        dprintk("dvb_ca: Skipping unknown tuple type:0x%x length:0x%x\n",
                                tuple_type, tuple_length);
                        break;
                }
        }

        if ((address > 0x1000) || (!got_cftableentry))
                return -EINVAL;

        dprintk("Valid DVB CAM detected MANID:%x DEVID:%x CONFIGBASE:0x%x CONFIGOPTION:0x%x\n",
                manfid, devid, sl->config_base, sl->config_option);

        /* success! */
        return 0;
}

/**
 * dvb_ca_en50221_set_configoption - Set CAM's configoption correctly.
 *
 * @ca: CA instance.
 * @slot: Slot containing the CAM.
 */
static int dvb_ca_en50221_set_configoption(struct dvb_ca_private *ca, int slot)
{
        struct dvb_ca_slot *sl = &ca->slot_info[slot];
        int configoption;

        dprintk("%s\n", __func__);

        /* set the config option */
        ca->pub->write_attribute_mem(ca->pub, slot, sl->config_base,
                                     sl->config_option);

        /* check it */
        configoption = ca->pub->read_attribute_mem(ca->pub, slot,
                                                   sl->config_base);
        dprintk("Set configoption 0x%x, read configoption 0x%x\n",
                sl->config_option, configoption & 0x3f);

        /* fine! */
        return 0;
}

/**
 * dvb_ca_en50221_read_data - This function talks to an EN50221 CAM control
 *      interface. It reads a buffer of data from the CAM. The data can either
 *      be stored in a supplied buffer, or automatically be added to the slot's
 *      rx_buffer.
 *
 * @ca: CA instance.
 * @slot: Slot to read from.
 * @ebuf: If non-NULL, the data will be written to this buffer. If NULL,
 *        the data will be added into the buffering system as a normal
 *        fragment.
 * @ecount: Size of ebuf. Ignored if ebuf is NULL.
 *
 * return: Number of bytes read, or < 0 on error
 */
static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot,
                                    u8 *ebuf, int ecount)
{
        struct dvb_ca_slot *sl = &ca->slot_info[slot];
        int bytes_read;
        int status;
        u8 buf[HOST_LINK_BUF_SIZE];
        int i;

        dprintk("%s\n", __func__);

        /* check if we have space for a link buf in the rx_buffer */
        if (!ebuf) {
                int buf_free;

                if (!sl->rx_buffer.data) {
                        status = -EIO;
                        goto exit;
                }
                buf_free = dvb_ringbuffer_free(&sl->rx_buffer);

                if (buf_free < (sl->link_buf_size +
                                DVB_RINGBUFFER_PKTHDRSIZE)) {
                        status = -EAGAIN;
                        goto exit;
                }
        }

        if (ca->pub->read_data &&
            (sl->slot_state != DVB_CA_SLOTSTATE_LINKINIT)) {
                if (!ebuf)
                        status = ca->pub->read_data(ca->pub, slot, buf,
                                                    sizeof(buf));
                else
                        status = ca->pub->read_data(ca->pub, slot, buf, ecount);
                if (status < 0)
                        return status;
                bytes_read =  status;
                if (status == 0)
                        goto exit;
        } else {
                /* check if there is data available */
                status = ca->pub->read_cam_control(ca->pub, slot,
                                                   CTRLIF_STATUS);
                if (status < 0)
                        goto exit;
                if (!(status & STATUSREG_DA)) {
                        /* no data */
                        status = 0;
                        goto exit;
                }

                /* read the amount of data */
                status = ca->pub->read_cam_control(ca->pub, slot,
                                                   CTRLIF_SIZE_HIGH);
                if (status < 0)
                        goto exit;
                bytes_read = status << 8;
                status = ca->pub->read_cam_control(ca->pub, slot,
                                                   CTRLIF_SIZE_LOW);
                if (status < 0)
                        goto exit;
                bytes_read |= status;

                /* check it will fit */
                if (!ebuf) {
                        if (bytes_read > sl->link_buf_size) {
                                pr_err("dvb_ca adapter %d: CAM tried to send a buffer larger than the link buffer size (%i > %i)!\n",
                                       ca->dvbdev->adapter->num, bytes_read,
                                       sl->link_buf_size);
                                sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT;
                                status = -EIO;
                                goto exit;
                        }
                        if (bytes_read < 2) {
                                pr_err("dvb_ca adapter %d: CAM sent a buffer that was less than 2 bytes!\n",
                                       ca->dvbdev->adapter->num);
                                sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT;
                                status = -EIO;
                                goto exit;
                        }
                } else {
                        if (bytes_read > ecount) {
                                pr_err("dvb_ca adapter %d: CAM tried to send a buffer larger than the ecount size!\n",
                                       ca->dvbdev->adapter->num);
                                status = -EIO;
                                goto exit;
                        }
                }

                /* fill the buffer */
                for (i = 0; i < bytes_read; i++) {
                        /* read byte and check */
                        status = ca->pub->read_cam_control(ca->pub, slot,
                                                           CTRLIF_DATA);
                        if (status < 0)
                                goto exit;

                        /* OK, store it in the buffer */
                        buf[i] = status;
                }

                /* check for read error (RE should now be 0) */
                status = ca->pub->read_cam_control(ca->pub, slot,
                                                   CTRLIF_STATUS);
                if (status < 0)
                        goto exit;
                if (status & STATUSREG_RE) {
                        sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT;
                        status = -EIO;
                        goto exit;
                }
        }

        /*
         * OK, add it to the receive buffer, or copy into external buffer if
         * supplied
         */
        if (!ebuf) {
                if (!sl->rx_buffer.data) {
                        status = -EIO;
                        goto exit;
                }
                dvb_ringbuffer_pkt_write(&sl->rx_buffer, buf, bytes_read);
        } else {
                memcpy(ebuf, buf, bytes_read);
        }

        dprintk("Received CA packet for slot %i connection id 0x%x last_frag:%i size:0x%x\n", slot,
                buf[0], (buf[1] & 0x80) == 0, bytes_read);

        /* wake up readers when a last_fragment is received */
        if ((buf[1] & 0x80) == 0x00)
                wake_up_interruptible(&ca->wait_queue);

        status = bytes_read;

exit:
        return status;
}

/**
 * dvb_ca_en50221_write_data - This function talks to an EN50221 CAM control
 *                              interface. It writes a buffer of data to a CAM.
 *
 * @ca: CA instance.
 * @slot: Slot to write to.
 * @buf: The data in this buffer is treated as a complete link-level packet to
 *       be written.
 * @bytes_write: Size of ebuf.
 * @size_write_flag: A flag on Command Register which says whether the link size
 * information will be written or not.
 *
 * return: Number of bytes written, or < 0 on error.
 */
static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot,
                                     u8 *buf, int bytes_write, int size_write_flag)
{
        struct dvb_ca_slot *sl = &ca->slot_info[slot];
        int status;
        int i;

        dprintk("%s\n", __func__);

        /* sanity check */
        if (bytes_write > sl->link_buf_size)
                return -EINVAL;

        if (ca->pub->write_data &&
            (sl->slot_state != DVB_CA_SLOTSTATE_LINKINIT))
                return ca->pub->write_data(ca->pub, slot, buf, bytes_write);

        /*
         * it is possible we are dealing with a single buffer implementation,
         * thus if there is data available for read or if there is even a read
         * already in progress, we do nothing but awake the kernel thread to
         * process the data if necessary.
         */
        status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS);
        if (status < 0)
                goto exitnowrite;
        if (status & (STATUSREG_DA | STATUSREG_RE)) {
                if (status & STATUSREG_DA)
                        dvb_ca_en50221_thread_wakeup(ca);

                status = -EAGAIN;
                goto exitnowrite;
        }

        /* OK, set HC bit */
        status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND,
                                            IRQEN | CMDREG_HC | size_write_flag);
        if (status)
                goto exit;

        /* check if interface is still free */
        status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS);
        if (status < 0)
                goto exit;
        if (!(status & STATUSREG_FR)) {
                /* it wasn't free => try again later */
                status = -EAGAIN;
                goto exit;
        }

        /*
         * It may need some time for the CAM to settle down, or there might
         * be a race condition between the CAM, writing HC and our last
         * check for DA. This happens, if the CAM asserts DA, just after
         * checking DA before we are setting HC. In this case it might be
         * a bug in the CAM to keep the FR bit, the lower layer/HW
         * communication requires a longer timeout or the CAM needs more
         * time internally. But this happens in reality!
         * We need to read the status from the HW again and do the same
         * we did for the previous check for DA
         */
        status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS);
        if (status < 0)
                goto exit;

        if (status & (STATUSREG_DA | STATUSREG_RE)) {
                if (status & STATUSREG_DA)
                        dvb_ca_en50221_thread_wakeup(ca);

                status = -EAGAIN;
                goto exit;
        }

        /* send the amount of data */
        status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_HIGH,
                                            bytes_write >> 8);
        if (status)
                goto exit;
        status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_LOW,
                                            bytes_write & 0xff);
        if (status)
                goto exit;

        /* send the buffer */
        for (i = 0; i < bytes_write; i++) {
                status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_DATA,
                                                    buf[i]);
                if (status)
                        goto exit;
        }

        /* check for write error (WE should now be 0) */
        status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS);
        if (status < 0)
                goto exit;
        if (status & STATUSREG_WE) {
                sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT;
                status = -EIO;
                goto exit;
        }
        status = bytes_write;

        dprintk("Wrote CA packet for slot %i, connection id 0x%x last_frag:%i size:0x%x\n", slot,
                buf[0], (buf[1] & 0x80) == 0, bytes_write);

exit:
        ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN);

exitnowrite:
        return status;
}

/* ************************************************************************** */
/* EN50221 higher level functions */

/**
 * dvb_ca_en50221_slot_shutdown - A CAM has been removed => shut it down.
 *
 * @ca: CA instance.
 * @slot: Slot to shut down.
 */
static int dvb_ca_en50221_slot_shutdown(struct dvb_ca_private *ca, int slot)
{
        dprintk("%s\n", __func__);

        ca->pub->slot_shutdown(ca->pub, slot);
        ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_NONE;

        /*
         * need to wake up all processes to check if they're now trying to
         * write to a defunct CAM
         */
        wake_up_interruptible(&ca->wait_queue);

        dprintk("Slot %i shutdown\n", slot);

        /* success */
        return 0;
}

/**
 * dvb_ca_en50221_camchange_irq - A CAMCHANGE IRQ has occurred.
 *
 * @pubca: CA instance.
 * @slot: Slot concerned.
 * @change_type: One of the DVB_CA_CAMCHANGE_* values.
 */
void dvb_ca_en50221_camchange_irq(struct dvb_ca_en50221 *pubca, int slot,
                                  int change_type)
{
        struct dvb_ca_private *ca = pubca->private;
        struct dvb_ca_slot *sl = &ca->slot_info[slot];

        dprintk("CAMCHANGE IRQ slot:%i change_type:%i\n", slot, change_type);

        switch (change_type) {
        case DVB_CA_EN50221_CAMCHANGE_REMOVED:
        case DVB_CA_EN50221_CAMCHANGE_INSERTED:
                break;

        default:
                return;
        }

        sl->camchange_type = change_type;
        atomic_inc(&sl->camchange_count);
        dvb_ca_en50221_thread_wakeup(ca);
}
EXPORT_SYMBOL(dvb_ca_en50221_camchange_irq);

/**
 * dvb_ca_en50221_camready_irq - A CAMREADY IRQ has occurred.
 *
 * @pubca: CA instance.
 * @slot: Slot concerned.
 */
void dvb_ca_en50221_camready_irq(struct dvb_ca_en50221 *pubca, int slot)
{
        struct dvb_ca_private *ca = pubca->private;
        struct dvb_ca_slot *sl = &ca->slot_info[slot];

        dprintk("CAMREADY IRQ slot:%i\n", slot);

        if (sl->slot_state == DVB_CA_SLOTSTATE_WAITREADY) {
                sl->slot_state = DVB_CA_SLOTSTATE_VALIDATE;
                dvb_ca_en50221_thread_wakeup(ca);
        }
}
EXPORT_SYMBOL(dvb_ca_en50221_camready_irq);

/**
 * dvb_ca_en50221_frda_irq - An FR or DA IRQ has occurred.
 *
 * @pubca: CA instance.
 * @slot: Slot concerned.
 */
void dvb_ca_en50221_frda_irq(struct dvb_ca_en50221 *pubca, int slot)
{
        struct dvb_ca_private *ca = pubca->private;
        struct dvb_ca_slot *sl = &ca->slot_info[slot];
        int flags;

        dprintk("FR/DA IRQ slot:%i\n", slot);

        switch (sl->slot_state) {
        case DVB_CA_SLOTSTATE_LINKINIT:
                flags = ca->pub->read_cam_control(pubca, slot, CTRLIF_STATUS);
                if (flags & STATUSREG_DA) {
                        dprintk("CAM supports DA IRQ\n");
                        sl->da_irq_supported = 1;
                }
                break;

        case DVB_CA_SLOTSTATE_RUNNING:
                if (ca->open)
                        dvb_ca_en50221_thread_wakeup(ca);
                break;
        }
}
EXPORT_SYMBOL(dvb_ca_en50221_frda_irq);

/* ************************************************************************** */
/* EN50221 thread functions */

/**
 * dvb_ca_en50221_thread_wakeup - Wake up the DVB CA thread
 *
 * @ca: CA instance.
 */
static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca)
{
        dprintk("%s\n", __func__);

        ca->wakeup = 1;
        mb();
        wake_up_process(ca->thread);
}

/**
 * dvb_ca_en50221_thread_update_delay - Update the delay used by the thread.
 *
 * @ca: CA instance.
 */
static void dvb_ca_en50221_thread_update_delay(struct dvb_ca_private *ca)
{
        int delay;
        int curdelay = 100000000;
        int slot;

        /*
         * Beware of too high polling frequency, because one polling
         * call might take several hundred milliseconds until timeout!
         */
        for (slot = 0; slot < ca->slot_count; slot++) {
                struct dvb_ca_slot *sl = &ca->slot_info[slot];

                switch (sl->slot_state) {
                default:
                case DVB_CA_SLOTSTATE_NONE:
                        delay = HZ * 60;  /* 60s */
                        if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
                                delay = HZ * 5;  /* 5s */
                        break;
                case DVB_CA_SLOTSTATE_INVALID:
                        delay = HZ * 60;  /* 60s */
                        if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
                                delay = HZ / 10;  /* 100ms */
                        break;

                case DVB_CA_SLOTSTATE_UNINITIALISED:
                case DVB_CA_SLOTSTATE_WAITREADY:
                case DVB_CA_SLOTSTATE_VALIDATE:
                case DVB_CA_SLOTSTATE_WAITFR:
                case DVB_CA_SLOTSTATE_LINKINIT:
                        delay = HZ / 10;  /* 100ms */
                        break;

                case DVB_CA_SLOTSTATE_RUNNING:
                        delay = HZ * 60;  /* 60s */
                        if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
                                delay = HZ / 10;  /* 100ms */
                        if (ca->open) {
                                if ((!sl->da_irq_supported) ||
                                    (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_DA)))
                                        delay = HZ / 10;  /* 100ms */
                        }
                        break;
                }

                if (delay < curdelay)
                        curdelay = delay;
        }

        ca->delay = curdelay;
}

/**
 * dvb_ca_en50221_poll_cam_gone - Poll if the CAM is gone.
 *
 * @ca: CA instance.
 * @slot: Slot to process.
 * return:: 0 .. no change
 *          1 .. CAM state changed
 */

static int dvb_ca_en50221_poll_cam_gone(struct dvb_ca_private *ca, int slot)
{
        int changed = 0;
        int status;

        /*
         * we need this extra check for annoying interfaces like the
         * budget-av
         */
        if ((!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)) &&
            (ca->pub->poll_slot_status)) {
                status = ca->pub->poll_slot_status(ca->pub, slot, 0);
                if (!(status &
                        DVB_CA_EN50221_POLL_CAM_PRESENT)) {
                        ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_NONE;
                        dvb_ca_en50221_thread_update_delay(ca);
                        changed = 1;
                }
        }
        return changed;
}

/**
 * dvb_ca_en50221_thread_state_machine - Thread state machine for one CA slot
 *      to perform the data transfer.
 *
 * @ca: CA instance.
 * @slot: Slot to process.
 */
static void dvb_ca_en50221_thread_state_machine(struct dvb_ca_private *ca,
                                                int slot)
{
        struct dvb_ca_slot *sl = &ca->slot_info[slot];
        int flags;
        int pktcount;
        void *rxbuf;

        mutex_lock(&sl->slot_lock);

        /* check the cam status + deal with CAMCHANGEs */
        while (dvb_ca_en50221_check_camstatus(ca, slot)) {
                /* clear down an old CI slot if necessary */
                if (sl->slot_state != DVB_CA_SLOTSTATE_NONE)
                        dvb_ca_en50221_slot_shutdown(ca, slot);

                /* if a CAM is NOW present, initialise it */
                if (sl->camchange_type == DVB_CA_EN50221_CAMCHANGE_INSERTED)
                        sl->slot_state = DVB_CA_SLOTSTATE_UNINITIALISED;

                /* we've handled one CAMCHANGE */
                dvb_ca_en50221_thread_update_delay(ca);
                atomic_dec(&sl->camchange_count);
        }

        /* CAM state machine */
        switch (sl->slot_state) {
        case DVB_CA_SLOTSTATE_NONE:
        case DVB_CA_SLOTSTATE_INVALID:
                /* no action needed */
                break;

        case DVB_CA_SLOTSTATE_UNINITIALISED:
                sl->slot_state = DVB_CA_SLOTSTATE_WAITREADY;
                ca->pub->slot_reset(ca->pub, slot);
                sl->timeout = jiffies + (INIT_TIMEOUT_SECS * HZ);
                break;

        case DVB_CA_SLOTSTATE_WAITREADY:
                if (time_after(jiffies, sl->timeout)) {
                        pr_err("dvb_ca adaptor %d: PC card did not respond :(\n",
                               ca->dvbdev->adapter->num);
                        sl->slot_state = DVB_CA_SLOTSTATE_INVALID;
                        dvb_ca_en50221_thread_update_delay(ca);
                        break;
                }
                /*
                 * no other action needed; will automatically change state when
                 * ready
                 */
                break;

        case DVB_CA_SLOTSTATE_VALIDATE:
                if (dvb_ca_en50221_parse_attributes(ca, slot) != 0) {
                        if (dvb_ca_en50221_poll_cam_gone(ca, slot))
                                break;

                        pr_err("dvb_ca adapter %d: Invalid PC card inserted :(\n",
                               ca->dvbdev->adapter->num);
                        sl->slot_state = DVB_CA_SLOTSTATE_INVALID;
                        dvb_ca_en50221_thread_update_delay(ca);
                        break;
                }
                if (dvb_ca_en50221_set_configoption(ca, slot) != 0) {
                        pr_err("dvb_ca adapter %d: Unable to initialise CAM :(\n",
                               ca->dvbdev->adapter->num);
                        sl->slot_state = DVB_CA_SLOTSTATE_INVALID;
                        dvb_ca_en50221_thread_update_delay(ca);
                        break;
                }
                if (ca->pub->write_cam_control(ca->pub, slot,
                                               CTRLIF_COMMAND,
                                               CMDREG_RS) != 0) {
                        pr_err("dvb_ca adapter %d: Unable to reset CAM IF\n",
                               ca->dvbdev->adapter->num);
                        sl->slot_state = DVB_CA_SLOTSTATE_INVALID;
                        dvb_ca_en50221_thread_update_delay(ca);
                        break;
                }
                dprintk("DVB CAM validated successfully\n");

                sl->timeout = jiffies + (INIT_TIMEOUT_SECS * HZ);
                sl->slot_state = DVB_CA_SLOTSTATE_WAITFR;
                ca->wakeup = 1;
                break;

        case DVB_CA_SLOTSTATE_WAITFR:
                if (time_after(jiffies, sl->timeout)) {
                        pr_err("dvb_ca adapter %d: DVB CAM did not respond :(\n",
                               ca->dvbdev->adapter->num);
                        sl->slot_state = DVB_CA_SLOTSTATE_INVALID;
                        dvb_ca_en50221_thread_update_delay(ca);
                        break;
                }

                flags = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS);
                if (flags & STATUSREG_FR) {
                        sl->slot_state = DVB_CA_SLOTSTATE_LINKINIT;
                        ca->wakeup = 1;
                }
                break;

        case DVB_CA_SLOTSTATE_LINKINIT:
                if (dvb_ca_en50221_link_init(ca, slot) != 0) {
                        if (dvb_ca_en50221_poll_cam_gone(ca, slot))
                                break;

                        pr_err("dvb_ca adapter %d: DVB CAM link initialisation failed :(\n",
                               ca->dvbdev->adapter->num);
                        sl->slot_state = DVB_CA_SLOTSTATE_UNINITIALISED;
                        dvb_ca_en50221_thread_update_delay(ca);
                        break;
                }

                if (!sl->rx_buffer.data) {
                        rxbuf = vmalloc(RX_BUFFER_SIZE);
                        if (!rxbuf) {
                                pr_err("dvb_ca adapter %d: Unable to allocate CAM rx buffer :(\n",
                                       ca->dvbdev->adapter->num);
                                sl->slot_state = DVB_CA_SLOTSTATE_INVALID;
                                dvb_ca_en50221_thread_update_delay(ca);
                                break;
                        }
                        dvb_ringbuffer_init(&sl->rx_buffer, rxbuf,
                                            RX_BUFFER_SIZE);
                }

                ca->pub->slot_ts_enable(ca->pub, slot);
                sl->slot_state = DVB_CA_SLOTSTATE_RUNNING;
                dvb_ca_en50221_thread_update_delay(ca);
                pr_info("dvb_ca adapter %d: DVB CAM detected and initialised successfully\n",
                        ca->dvbdev->adapter->num);
                break;

        case DVB_CA_SLOTSTATE_RUNNING:
                if (!ca->open)
                        break;

                /* poll slots for data */
                pktcount = 0;
                while (dvb_ca_en50221_read_data(ca, slot, NULL, 0) > 0) {
                        if (!ca->open)
                                break;

                        /*
                         * if a CAMCHANGE occurred at some point, do not do any
                         * more processing of this slot
                         */
                        if (dvb_ca_en50221_check_camstatus(ca, slot)) {
                                /*
                                 * we don't want to sleep on the next iteration
                                 * so we can handle the cam change
                                 */
                                ca->wakeup = 1;
                                break;
                        }

                        /* check if we've hit our limit this time */
                        if (++pktcount >= MAX_RX_PACKETS_PER_ITERATION) {
                                /*
                                 * don't sleep; there is likely to be more data
                                 * to read
                                 */
                                ca->wakeup = 1;
                                break;
                        }
                }
                break;
        }

        mutex_unlock(&sl->slot_lock);
}

/*
 * Kernel thread which monitors CA slots for CAM changes, and performs data
 * transfers.
 */
static int dvb_ca_en50221_thread(void *data)
{
        struct dvb_ca_private *ca = data;
        int slot;

        dprintk("%s\n", __func__);

        /* choose the correct initial delay */
        dvb_ca_en50221_thread_update_delay(ca);

        /* main loop */
        while (!kthread_should_stop()) {
                /* sleep for a bit */
                if (!ca->wakeup) {
                        set_current_state(TASK_INTERRUPTIBLE);
                        schedule_timeout(ca->delay);
                        if (kthread_should_stop())
                                return 0;
                }
                ca->wakeup = 0;

                /* go through all the slots processing them */
                for (slot = 0; slot < ca->slot_count; slot++)
                        dvb_ca_en50221_thread_state_machine(ca, slot);
        }

        return 0;
}

/* ************************************************************************** */
/* EN50221 IO interface functions */

/**
 * dvb_ca_en50221_io_do_ioctl - Real ioctl implementation.
 *
 * @file: File concerned.
 * @cmd: IOCTL command.
 * @parg: Associated argument.
 *
 * NOTE: CA_SEND_MSG/CA_GET_MSG ioctls have userspace buffers passed to them.
 *
 * return: 0 on success, <0 on error.
 */
static int dvb_ca_en50221_io_do_ioctl(struct file *file,
                                      unsigned int cmd, void *parg)
{
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_ca_private *ca = dvbdev->priv;
        int err = 0;
        int slot;

        dprintk("%s\n", __func__);

        if (mutex_lock_interruptible(&ca->ioctl_mutex))
                return -ERESTARTSYS;

        switch (cmd) {
        case CA_RESET:
                for (slot = 0; slot < ca->slot_count; slot++) {
                        struct dvb_ca_slot *sl = &ca->slot_info[slot];

                        mutex_lock(&sl->slot_lock);
                        if (sl->slot_state != DVB_CA_SLOTSTATE_NONE) {
                                dvb_ca_en50221_slot_shutdown(ca, slot);
                                if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)
                                        dvb_ca_en50221_camchange_irq(ca->pub,
                                                                     slot,
                                                                     DVB_CA_EN50221_CAMCHANGE_INSERTED);
                        }
                        mutex_unlock(&sl->slot_lock);
                }
                ca->next_read_slot = 0;
                dvb_ca_en50221_thread_wakeup(ca);
                break;

        case CA_GET_CAP: {
                struct ca_caps *caps = parg;

                caps->slot_num = ca->slot_count;
                caps->slot_type = CA_CI_LINK;
                caps->descr_num = 0;
                caps->descr_type = 0;
                break;
        }

        case CA_GET_SLOT_INFO: {
                struct ca_slot_info *info = parg;
                struct dvb_ca_slot *sl;

                slot = info->num;
                if ((slot >= ca->slot_count) || (slot < 0)) {
                        err = -EINVAL;
                        goto out_unlock;
                }
                slot = array_index_nospec(slot, ca->slot_count);

                info->type = CA_CI_LINK;
                info->flags = 0;
                sl = &ca->slot_info[slot];
                if ((sl->slot_state != DVB_CA_SLOTSTATE_NONE) &&
                    (sl->slot_state != DVB_CA_SLOTSTATE_INVALID)) {
                        info->flags = CA_CI_MODULE_PRESENT;
                }
                if (sl->slot_state == DVB_CA_SLOTSTATE_RUNNING)
                        info->flags |= CA_CI_MODULE_READY;
                break;
        }

        default:
                err = -EINVAL;
                break;
        }

out_unlock:
        mutex_unlock(&ca->ioctl_mutex);
        return err;
}

/**
 * dvb_ca_en50221_io_ioctl - Wrapper for ioctl implementation.
 *
 * @file: File concerned.
 * @cmd: IOCTL command.
 * @arg: Associated argument.
 *
 * return: 0 on success, <0 on error.
 */
static long dvb_ca_en50221_io_ioctl(struct file *file,
                                    unsigned int cmd, unsigned long arg)
{
        return dvb_usercopy(file, cmd, arg, dvb_ca_en50221_io_do_ioctl);
}

/**
 * dvb_ca_en50221_io_write - Implementation of write() syscall.
 *
 * @file: File structure.
 * @buf: Source buffer.
 * @count: Size of source buffer.
 * @ppos: Position in file (ignored).
 *
 * return: Number of bytes read, or <0 on error.
 */
static ssize_t dvb_ca_en50221_io_write(struct file *file,
                                       const char __user *buf, size_t count,
                                       loff_t *ppos)
{
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_ca_private *ca = dvbdev->priv;
        struct dvb_ca_slot *sl;
        u8 slot, connection_id;
        int status;
        u8 fragbuf[HOST_LINK_BUF_SIZE];
        int fragpos = 0;
        int fraglen;
        unsigned long timeout;
        int written;

        dprintk("%s\n", __func__);

        /*
         * Incoming packet has a 2 byte header.
         * hdr[0] = slot_id, hdr[1] = connection_id
         */
        if (count < 2)
                return -EINVAL;

        /* extract slot & connection id */
        if (copy_from_user(&slot, buf, 1))
                return -EFAULT;
        if (copy_from_user(&connection_id, buf + 1, 1))
                return -EFAULT;
        buf += 2;
        count -= 2;

        if (slot >= ca->slot_count)
                return -EINVAL;
        slot = array_index_nospec(slot, ca->slot_count);
        sl = &ca->slot_info[slot];

        /* check if the slot is actually running */
        if (sl->slot_state != DVB_CA_SLOTSTATE_RUNNING)
                return -EINVAL;

        /* fragment the packets & store in the buffer */
        while (fragpos < count) {
                fraglen = sl->link_buf_size - 2;
                if (fraglen < 0)
                        break;
                if (fraglen > HOST_LINK_BUF_SIZE - 2)
                        fraglen = HOST_LINK_BUF_SIZE - 2;
                if ((count - fragpos) < fraglen)
                        fraglen = count - fragpos;

                fragbuf[0] = connection_id;
                fragbuf[1] = ((fragpos + fraglen) < count) ? 0x80 : 0x00;
                status = copy_from_user(fragbuf + 2, buf + fragpos, fraglen);
                if (status) {
                        status = -EFAULT;
                        goto exit;
                }

                timeout = jiffies + HZ / 2;
                written = 0;
                while (!time_after(jiffies, timeout)) {
                        /*
                         * check the CAM hasn't been removed/reset in the
                         * meantime
                         */
                        if (sl->slot_state != DVB_CA_SLOTSTATE_RUNNING) {
                                status = -EIO;
                                goto exit;
                        }

                        mutex_lock(&sl->slot_lock);
                        status = dvb_ca_en50221_write_data(ca, slot, fragbuf,
                                                           fraglen + 2, 0);
                        mutex_unlock(&sl->slot_lock);
                        if (status == (fraglen + 2)) {
                                written = 1;
                                break;
                        }
                        if (status != -EAGAIN)
                                goto exit;

                        usleep_range(1000, 1100);
                }
                if (!written) {
                        status = -EIO;
                        goto exit;
                }

                fragpos += fraglen;
        }
        status = count + 2;

exit:
        return status;
}

/*
 * Condition for waking up in dvb_ca_en50221_io_read_condition
 */
static int dvb_ca_en50221_io_read_condition(struct dvb_ca_private *ca,
                                            int *result, int *_slot)
{
        int slot;
        int slot_count = 0;
        int idx;
        size_t fraglen;
        int connection_id = -1;
        int found = 0;
        u8 hdr[2];

        slot = ca->next_read_slot;
        while ((slot_count < ca->slot_count) && (!found)) {
                struct dvb_ca_slot *sl = &ca->slot_info[slot];

                if (sl->slot_state != DVB_CA_SLOTSTATE_RUNNING)
                        goto nextslot;

                if (!sl->rx_buffer.data)
                        return 0;

                idx = dvb_ringbuffer_pkt_next(&sl->rx_buffer, -1, &fraglen);
                while (idx != -1) {
                        dvb_ringbuffer_pkt_read(&sl->rx_buffer, idx, 0, hdr, 2);
                        if (connection_id == -1)
                                connection_id = hdr[0];
                        if ((hdr[0] == connection_id) &&
                            ((hdr[1] & 0x80) == 0)) {
                                *_slot = slot;
                                found = 1;
                                break;
                        }

                        idx = dvb_ringbuffer_pkt_next(&sl->rx_buffer, idx,
                                                      &fraglen);
                }

nextslot:
                slot = (slot + 1) % ca->slot_count;
                slot_count++;
        }

        ca->next_read_slot = slot;
        return found;
}

/**
 * dvb_ca_en50221_io_read - Implementation of read() syscall.
 *
 * @file: File structure.
 * @buf: Destination buffer.
 * @count: Size of destination buffer.
 * @ppos: Position in file (ignored).
 *
 * return: Number of bytes read, or <0 on error.
 */
static ssize_t dvb_ca_en50221_io_read(struct file *file, char __user *buf,
                                      size_t count, loff_t *ppos)
{
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_ca_private *ca = dvbdev->priv;
        struct dvb_ca_slot *sl;
        int status;
        int result = 0;
        u8 hdr[2];
        int slot;
        int connection_id = -1;
        size_t idx, idx2;
        int last_fragment = 0;
        size_t fraglen;
        int pktlen;
        int dispose = 0;

        dprintk("%s\n", __func__);

        /*
         * Outgoing packet has a 2 byte header.
         * hdr[0] = slot_id, hdr[1] = connection_id
         */
        if (count < 2)
                return -EINVAL;

        /* wait for some data */
        status = dvb_ca_en50221_io_read_condition(ca, &result, &slot);
        if (status == 0) {
                /* if we're in nonblocking mode, exit immediately */
                if (file->f_flags & O_NONBLOCK)
                        return -EWOULDBLOCK;

                /* wait for some data */
                status = wait_event_interruptible(ca->wait_queue,
                                                  dvb_ca_en50221_io_read_condition
                                                  (ca, &result, &slot));
        }
        if ((status < 0) || (result < 0)) {
                if (result)
                        return result;
                return status;
        }

        sl = &ca->slot_info[slot];
        idx = dvb_ringbuffer_pkt_next(&sl->rx_buffer, -1, &fraglen);
        pktlen = 2;
        do {
                if (idx == -1) {
                        pr_err("dvb_ca adapter %d: BUG: read packet ended before last_fragment encountered\n",
                               ca->dvbdev->adapter->num);
                        status = -EIO;
                        goto exit;
                }

                dvb_ringbuffer_pkt_read(&sl->rx_buffer, idx, 0, hdr, 2);
                if (connection_id == -1)
                        connection_id = hdr[0];
                if (hdr[0] == connection_id) {
                        if (pktlen < count) {
                                if ((pktlen + fraglen - 2) > count)
                                        fraglen = count - pktlen;
                                else
                                        fraglen -= 2;

                                status =
                                   dvb_ringbuffer_pkt_read_user(&sl->rx_buffer,
                                                                idx, 2,
                                                                buf + pktlen,
                                                                fraglen);
                                if (status < 0)
                                        goto exit;

                                pktlen += fraglen;
                        }

                        if ((hdr[1] & 0x80) == 0)
                                last_fragment = 1;
                        dispose = 1;
                }

                idx2 = dvb_ringbuffer_pkt_next(&sl->rx_buffer, idx, &fraglen);
                if (dispose)
                        dvb_ringbuffer_pkt_dispose(&sl->rx_buffer, idx);
                idx = idx2;
                dispose = 0;
        } while (!last_fragment);

        hdr[0] = slot;
        hdr[1] = connection_id;
        status = copy_to_user(buf, hdr, 2);
        if (status) {
                status = -EFAULT;
                goto exit;
        }
        status = pktlen;

exit:
        return status;
}

/**
 * dvb_ca_en50221_io_open - Implementation of file open syscall.
 *
 * @inode: Inode concerned.
 * @file: File concerned.
 *
 * return: 0 on success, <0 on failure.
 */
static int dvb_ca_en50221_io_open(struct inode *inode, struct file *file)
{
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_ca_private *ca = dvbdev->priv;
        int err;
        int i;

        dprintk("%s\n", __func__);

        mutex_lock(&ca->remove_mutex);

        if (ca->exit) {
                mutex_unlock(&ca->remove_mutex);
                return -ENODEV;
        }

        if (!try_module_get(ca->pub->owner)) {
                mutex_unlock(&ca->remove_mutex);
                return -EIO;
        }

        err = dvb_generic_open(inode, file);
        if (err < 0) {
                module_put(ca->pub->owner);
                mutex_unlock(&ca->remove_mutex);
                return err;
        }

        for (i = 0; i < ca->slot_count; i++) {
                struct dvb_ca_slot *sl = &ca->slot_info[i];

                if (sl->slot_state == DVB_CA_SLOTSTATE_RUNNING) {
                        if (!sl->rx_buffer.data) {
                                /*
                                 * it is safe to call this here without locks
                                 * because ca->open == 0. Data is not read in
                                 * this case
                                 */
                                dvb_ringbuffer_flush(&sl->rx_buffer);
                        }
                }
        }

        ca->open = 1;
        dvb_ca_en50221_thread_update_delay(ca);
        dvb_ca_en50221_thread_wakeup(ca);

        dvb_ca_private_get(ca);

        mutex_unlock(&ca->remove_mutex);
        return 0;
}

/**
 * dvb_ca_en50221_io_release - Implementation of file close syscall.
 *
 * @inode: Inode concerned.
 * @file: File concerned.
 *
 * return: 0 on success, <0 on failure.
 */
static int dvb_ca_en50221_io_release(struct inode *inode, struct file *file)
{
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_ca_private *ca = dvbdev->priv;
        int err;

        dprintk("%s\n", __func__);

        mutex_lock(&ca->remove_mutex);

        /* mark the CA device as closed */
        ca->open = 0;
        dvb_ca_en50221_thread_update_delay(ca);

        err = dvb_generic_release(inode, file);

        module_put(ca->pub->owner);

        dvb_ca_private_put(ca);

        if (dvbdev->users == 1 && ca->exit == 1) {
                mutex_unlock(&ca->remove_mutex);
                wake_up(&dvbdev->wait_queue);
        } else {
                mutex_unlock(&ca->remove_mutex);
        }

        return err;
}

/**
 * dvb_ca_en50221_io_poll - Implementation of poll() syscall.
 *
 * @file: File concerned.
 * @wait: poll wait table.
 *
 * return: Standard poll mask.
 */
static __poll_t dvb_ca_en50221_io_poll(struct file *file, poll_table *wait)
{
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_ca_private *ca = dvbdev->priv;
        __poll_t mask = 0;
        int slot;
        int result = 0;

        dprintk("%s\n", __func__);

        poll_wait(file, &ca->wait_queue, wait);

        if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1)
                mask |= EPOLLIN;

        /* if there is something, return now */
        if (mask)
                return mask;

        if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1)
                mask |= EPOLLIN;

        return mask;
}

static const struct file_operations dvb_ca_fops = {
        .owner = THIS_MODULE,
        .read = dvb_ca_en50221_io_read,
        .write = dvb_ca_en50221_io_write,
        .unlocked_ioctl = dvb_ca_en50221_io_ioctl,
        .open = dvb_ca_en50221_io_open,
        .release = dvb_ca_en50221_io_release,
        .poll = dvb_ca_en50221_io_poll,
        .llseek = noop_llseek,
};

static const struct dvb_device dvbdev_ca = {
        .priv = NULL,
        .users = 1,
        .readers = 1,
        .writers = 1,
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
        .name = "dvb-ca-en50221",
#endif
        .fops = &dvb_ca_fops,
};

/* ************************************************************************** */
/* Initialisation/shutdown functions */

/**
 * dvb_ca_en50221_init - Initialise a new DVB CA EN50221 interface device.
 *
 * @dvb_adapter: DVB adapter to attach the new CA device to.
 * @pubca: The dvb_ca instance.
 * @flags: Flags describing the CA device (DVB_CA_FLAG_*).
 * @slot_count: Number of slots supported.
 *
 * return: 0 on success, nonzero on failure
 */
int dvb_ca_en50221_init(struct dvb_adapter *dvb_adapter,
                        struct dvb_ca_en50221 *pubca, int flags, int slot_count)
{
        int ret;
        struct dvb_ca_private *ca = NULL;
        int i;

        dprintk("%s\n", __func__);

        if (slot_count < 1)
                return -EINVAL;

        /* initialise the system data */
        ca = kzalloc_obj(*ca);
        if (!ca) {
                ret = -ENOMEM;
                goto exit;
        }
        kref_init(&ca->refcount);
        ca->pub = pubca;
        ca->flags = flags;
        ca->slot_count = slot_count;
        ca->slot_info = kzalloc_objs(struct dvb_ca_slot, slot_count);
        if (!ca->slot_info) {
                ret = -ENOMEM;
                goto free_ca;
        }
        init_waitqueue_head(&ca->wait_queue);
        ca->open = 0;
        ca->wakeup = 0;
        ca->next_read_slot = 0;
        pubca->private = ca;

        /* register the DVB device */
        ret = dvb_register_device(dvb_adapter, &ca->dvbdev, &dvbdev_ca, ca,
                                  DVB_DEVICE_CA, 0);
        if (ret)
                goto free_slot_info;

        /* now initialise each slot */
        for (i = 0; i < slot_count; i++) {
                struct dvb_ca_slot *sl = &ca->slot_info[i];

                memset(sl, 0, sizeof(struct dvb_ca_slot));
                sl->slot_state = DVB_CA_SLOTSTATE_NONE;
                atomic_set(&sl->camchange_count, 0);
                sl->camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED;
                mutex_init(&sl->slot_lock);
        }

        mutex_init(&ca->ioctl_mutex);
        mutex_init(&ca->remove_mutex);

        if (signal_pending(current)) {
                ret = -EINTR;
                goto unregister_device;
        }
        mb();

        /* create a kthread for monitoring this CA device */
        ca->thread = kthread_run(dvb_ca_en50221_thread, ca, "kdvb-ca-%i:%i",
                                 ca->dvbdev->adapter->num, ca->dvbdev->id);
        if (IS_ERR(ca->thread)) {
                ret = PTR_ERR(ca->thread);
                pr_err("dvb_ca_init: failed to start kernel_thread (%d)\n",
                       ret);
                goto unregister_device;
        }
        return 0;

unregister_device:
        dvb_unregister_device(ca->dvbdev);
free_slot_info:
        kfree(ca->slot_info);
free_ca:
        kfree(ca);
exit:
        pubca->private = NULL;
        return ret;
}
EXPORT_SYMBOL(dvb_ca_en50221_init);

/**
 * dvb_ca_en50221_release - Release a DVB CA EN50221 interface device.
 *
 * @pubca: The associated dvb_ca instance.
 */
void dvb_ca_en50221_release(struct dvb_ca_en50221 *pubca)
{
        struct dvb_ca_private *ca = pubca->private;
        int i;

        dprintk("%s\n", __func__);

        mutex_lock(&ca->remove_mutex);
        ca->exit = 1;
        mutex_unlock(&ca->remove_mutex);

        if (ca->dvbdev->users < 1)
                wait_event(ca->dvbdev->wait_queue,
                                ca->dvbdev->users == 1);

        /* shutdown the thread if there was one */
        kthread_stop(ca->thread);

        for (i = 0; i < ca->slot_count; i++)
                dvb_ca_en50221_slot_shutdown(ca, i);

        dvb_remove_device(ca->dvbdev);
        dvb_ca_private_put(ca);
        pubca->private = NULL;
}
EXPORT_SYMBOL(dvb_ca_en50221_release);