mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-03 05:36:40 +07:00
df2f94e563
The Media Controller New Generation redefines the types for both interfaces and entities to be used on DVB. Make the needed changes at the DVB core for all interfaces, entities and data and interface links to appear in the graph. Acked-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
816 lines
19 KiB
C
816 lines
19 KiB
C
/*
|
|
* 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 *new_fops;
|
|
|
|
new_fops = fops_get(dvbdev->fops);
|
|
if (!new_fops)
|
|
goto fail;
|
|
file->private_data = dvbdev;
|
|
replace_fops(file, new_fops);
|
|
if (file->f_op->open)
|
|
err = file->f_op->open(inode,file);
|
|
up_read(&minor_rwsem);
|
|
mutex_unlock(&dvbdev_mutex);
|
|
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;
|
|
}
|
|
|
|
static void dvb_create_tsout_entity(struct dvb_device *dvbdev,
|
|
const char *name, int npads)
|
|
{
|
|
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
|
|
int i, ret = 0;
|
|
|
|
dvbdev->tsout_pads = kcalloc(npads, sizeof(*dvbdev->tsout_pads),
|
|
GFP_KERNEL);
|
|
if (!dvbdev->tsout_pads)
|
|
return;
|
|
dvbdev->tsout_entity = kcalloc(npads, sizeof(*dvbdev->tsout_entity),
|
|
GFP_KERNEL);
|
|
if (!dvbdev->tsout_entity) {
|
|
kfree(dvbdev->tsout_pads);
|
|
dvbdev->tsout_pads = NULL;
|
|
return;
|
|
}
|
|
for (i = 0; i < npads; i++) {
|
|
struct media_pad *pads = &dvbdev->tsout_pads[i];
|
|
struct media_entity *entity = &dvbdev->tsout_entity[i];
|
|
|
|
entity->name = kasprintf(GFP_KERNEL, "%s #%d", name, i);
|
|
if (!entity->name) {
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
entity->type = MEDIA_ENT_T_DVB_TSOUT;
|
|
pads->flags = MEDIA_PAD_FL_SINK;
|
|
|
|
ret = media_entity_init(entity, 1, pads);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
ret = media_device_register_entity(dvbdev->adapter->mdev,
|
|
entity);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
|
|
if (!ret) {
|
|
dvbdev->tsout_num_entities = npads;
|
|
return;
|
|
}
|
|
|
|
for (i--; i >= 0; i--) {
|
|
media_device_unregister_entity(&dvbdev->tsout_entity[i]);
|
|
kfree(dvbdev->tsout_entity[i].name);
|
|
}
|
|
|
|
printk(KERN_ERR
|
|
"%s: media_device_register_entity failed for %s\n",
|
|
__func__, name);
|
|
|
|
kfree(dvbdev->tsout_entity);
|
|
kfree(dvbdev->tsout_pads);
|
|
dvbdev->tsout_entity = NULL;
|
|
dvbdev->tsout_pads = NULL;
|
|
#endif
|
|
}
|
|
|
|
#define DEMUX_TSOUT "demux-tsout"
|
|
#define DVR_TSOUT "dvr-tsout"
|
|
|
|
static void dvb_create_media_entity(struct dvb_device *dvbdev,
|
|
int type, int demux_sink_pads)
|
|
{
|
|
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
|
|
int i, ret = 0, npads;
|
|
|
|
switch (type) {
|
|
case DVB_DEVICE_FRONTEND:
|
|
npads = 2;
|
|
break;
|
|
case DVB_DEVICE_DVR:
|
|
dvb_create_tsout_entity(dvbdev, DVR_TSOUT, demux_sink_pads);
|
|
return;
|
|
case DVB_DEVICE_DEMUX:
|
|
npads = 1 + demux_sink_pads;
|
|
dvb_create_tsout_entity(dvbdev, DEMUX_TSOUT, demux_sink_pads);
|
|
break;
|
|
case DVB_DEVICE_CA:
|
|
npads = 2;
|
|
break;
|
|
case DVB_DEVICE_NET:
|
|
/*
|
|
* We should be creating entities for the MPE/ULE
|
|
* decapsulation hardware (or software implementation).
|
|
*
|
|
* However, the number of for the MPE/ULE decaps may not be
|
|
* fixed. As we don't have yet dynamic support for PADs at
|
|
* the Media Controller, let's not create the decap
|
|
* entities yet.
|
|
*/
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
dvbdev->entity = kzalloc(sizeof(*dvbdev->entity), GFP_KERNEL);
|
|
if (!dvbdev->entity)
|
|
return;
|
|
|
|
dvbdev->entity->name = dvbdev->name;
|
|
|
|
if (npads) {
|
|
dvbdev->pads = kcalloc(npads, sizeof(*dvbdev->pads),
|
|
GFP_KERNEL);
|
|
if (!dvbdev->pads) {
|
|
kfree(dvbdev->entity);
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case DVB_DEVICE_FRONTEND:
|
|
dvbdev->entity->type = MEDIA_ENT_T_DVB_DEMOD;
|
|
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
|
|
dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE;
|
|
break;
|
|
case DVB_DEVICE_DEMUX:
|
|
dvbdev->entity->type = MEDIA_ENT_T_DVB_DEMUX;
|
|
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
|
|
for (i = 1; i < npads; i++)
|
|
dvbdev->pads[i].flags = MEDIA_PAD_FL_SOURCE;
|
|
break;
|
|
case DVB_DEVICE_CA:
|
|
dvbdev->entity->type = MEDIA_ENT_T_DVB_CA;
|
|
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
|
|
dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE;
|
|
break;
|
|
default:
|
|
kfree(dvbdev->entity);
|
|
dvbdev->entity = NULL;
|
|
return;
|
|
}
|
|
|
|
if (npads)
|
|
ret = media_entity_init(dvbdev->entity, npads, dvbdev->pads);
|
|
if (!ret)
|
|
ret = media_device_register_entity(dvbdev->adapter->mdev,
|
|
dvbdev->entity);
|
|
if (ret < 0) {
|
|
printk(KERN_ERR
|
|
"%s: media_device_register_entity failed for %s\n",
|
|
__func__, dvbdev->entity->name);
|
|
|
|
media_device_unregister_entity(dvbdev->entity);
|
|
for (i = 0; i < dvbdev->tsout_num_entities; i++) {
|
|
media_device_unregister_entity(&dvbdev->tsout_entity[i]);
|
|
kfree(dvbdev->tsout_entity[i].name);
|
|
}
|
|
kfree(dvbdev->pads);
|
|
kfree(dvbdev->entity);
|
|
kfree(dvbdev->tsout_pads);
|
|
kfree(dvbdev->tsout_entity);
|
|
dvbdev->entity = NULL;
|
|
return;
|
|
}
|
|
|
|
printk(KERN_DEBUG "%s: media entity '%s' registered.\n",
|
|
__func__, dvbdev->entity->name);
|
|
#endif
|
|
}
|
|
|
|
static void dvb_register_media_device(struct dvb_device *dvbdev,
|
|
int type, int minor,
|
|
unsigned demux_sink_pads)
|
|
{
|
|
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
|
|
u32 intf_type;
|
|
|
|
if (!dvbdev->adapter->mdev)
|
|
return;
|
|
|
|
dvb_create_media_entity(dvbdev, type, demux_sink_pads);
|
|
|
|
switch (type) {
|
|
case DVB_DEVICE_FRONTEND:
|
|
intf_type = MEDIA_INTF_T_DVB_FE;
|
|
break;
|
|
case DVB_DEVICE_DEMUX:
|
|
intf_type = MEDIA_INTF_T_DVB_DEMUX;
|
|
break;
|
|
case DVB_DEVICE_DVR:
|
|
intf_type = MEDIA_INTF_T_DVB_DVR;
|
|
break;
|
|
case DVB_DEVICE_CA:
|
|
intf_type = MEDIA_INTF_T_DVB_CA;
|
|
break;
|
|
case DVB_DEVICE_NET:
|
|
intf_type = MEDIA_INTF_T_DVB_NET;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
dvbdev->intf_devnode = media_devnode_create(dvbdev->adapter->mdev,
|
|
intf_type, 0,
|
|
DVB_MAJOR, minor,
|
|
GFP_KERNEL);
|
|
|
|
/*
|
|
* Create the "obvious" link, e. g. the ones that represent
|
|
* a direct association between an interface and an entity.
|
|
* Other links should be created elsewhere, like:
|
|
* DVB FE intf -> tuner
|
|
* DVB demux intf -> dvr
|
|
*/
|
|
|
|
if (!dvbdev->entity || !dvbdev->intf_devnode)
|
|
return;
|
|
|
|
media_create_intf_link(dvbdev->entity, &dvbdev->intf_devnode->intf, 0);
|
|
|
|
#endif
|
|
}
|
|
|
|
int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
|
|
const struct dvb_device *template, void *priv, int type,
|
|
int demux_sink_pads)
|
|
{
|
|
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);
|
|
|
|
dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
|
|
|
|
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));
|
|
|
|
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
|
|
if (dvbdev->entity) {
|
|
int i;
|
|
|
|
media_device_unregister_entity(dvbdev->entity);
|
|
for (i = 0; i < dvbdev->tsout_num_entities; i++) {
|
|
media_device_unregister_entity(&dvbdev->tsout_entity[i]);
|
|
kfree(dvbdev->tsout_entity[i].name);
|
|
}
|
|
|
|
kfree(dvbdev->entity);
|
|
kfree(dvbdev->pads);
|
|
kfree(dvbdev->tsout_entity);
|
|
kfree(dvbdev->tsout_pads);
|
|
}
|
|
#endif
|
|
|
|
list_del (&dvbdev->list_head);
|
|
kfree (dvbdev->fops);
|
|
kfree (dvbdev);
|
|
}
|
|
EXPORT_SYMBOL(dvb_unregister_device);
|
|
|
|
|
|
#ifdef CONFIG_MEDIA_CONTROLLER_DVB
|
|
void dvb_create_media_graph(struct dvb_adapter *adap)
|
|
{
|
|
struct media_device *mdev = adap->mdev;
|
|
struct media_entity *entity, *tuner = NULL, *demod = NULL;
|
|
struct media_entity *demux = NULL, *ca = NULL;
|
|
struct media_interface *intf;
|
|
unsigned demux_pad = 0;
|
|
unsigned dvr_pad = 0;
|
|
|
|
if (!mdev)
|
|
return;
|
|
|
|
media_device_for_each_entity(entity, mdev) {
|
|
switch (entity->type) {
|
|
case MEDIA_ENT_T_V4L2_SUBDEV_TUNER:
|
|
tuner = entity;
|
|
break;
|
|
case MEDIA_ENT_T_DVB_DEMOD:
|
|
demod = entity;
|
|
break;
|
|
case MEDIA_ENT_T_DVB_DEMUX:
|
|
demux = entity;
|
|
break;
|
|
case MEDIA_ENT_T_DVB_CA:
|
|
ca = entity;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tuner && demod)
|
|
media_create_pad_link(tuner, 0, demod, 0, 0);
|
|
|
|
if (demod && demux)
|
|
media_create_pad_link(demod, 1, demux, 0, MEDIA_LNK_FL_ENABLED);
|
|
if (demux && ca)
|
|
media_create_pad_link(demux, 1, ca, 0, MEDIA_LNK_FL_ENABLED);
|
|
|
|
/* Create demux links for each ringbuffer/pad */
|
|
if (demux) {
|
|
media_device_for_each_entity(entity, mdev) {
|
|
if (entity->type == MEDIA_ENT_T_DVB_TSOUT) {
|
|
if (!strncmp(entity->name, DVR_TSOUT,
|
|
strlen(DVR_TSOUT)))
|
|
media_create_pad_link(demux,
|
|
++dvr_pad,
|
|
entity, 0, 0);
|
|
if (!strncmp(entity->name, DEMUX_TSOUT,
|
|
strlen(DEMUX_TSOUT)))
|
|
media_create_pad_link(demux,
|
|
++demux_pad,
|
|
entity, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Create indirect interface links for FE->tuner, DVR->demux and CA->ca */
|
|
list_for_each_entry(intf, &mdev->interfaces, list) {
|
|
if (intf->type == MEDIA_INTF_T_DVB_CA && ca)
|
|
media_create_intf_link(ca, intf, 0);
|
|
if (intf->type == MEDIA_INTF_T_DVB_FE && tuner)
|
|
media_create_intf_link(tuner, intf, 0);
|
|
|
|
if (intf->type == MEDIA_INTF_T_DVB_DVR && demux)
|
|
media_create_intf_link(demux, intf, 0);
|
|
|
|
media_device_for_each_entity(entity, mdev) {
|
|
if (entity->type == MEDIA_ENT_T_DVB_TSOUT) {
|
|
if (!strcmp(entity->name, DVR_TSOUT))
|
|
media_create_intf_link(entity, intf, 0);
|
|
if (!strcmp(entity->name, DEMUX_TSOUT))
|
|
media_create_intf_link(entity, intf, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(dvb_create_media_graph);
|
|
#endif
|
|
|
|
static int dvbdev_check_free_adapter_num(int num)
|
|
{
|
|
struct list_head *entry;
|
|
list_for_each(entry, &dvb_adapter_list) {
|
|
struct dvb_adapter *adap;
|
|
adap = list_entry(entry, struct dvb_adapter, list_head);
|
|
if (adap->num == num)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int dvbdev_get_free_adapter_num (void)
|
|
{
|
|
int num = 0;
|
|
|
|
while (num < DVB_MAX_ADAPTERS) {
|
|
if (dvbdev_check_free_adapter_num(num))
|
|
return num;
|
|
num++;
|
|
}
|
|
|
|
return -ENFILE;
|
|
}
|
|
|
|
|
|
int dvb_register_adapter(struct dvb_adapter *adap, const char *name,
|
|
struct module *module, struct device *device,
|
|
short *adapter_nums)
|
|
{
|
|
int i, num;
|
|
|
|
mutex_lock(&dvbdev_register_lock);
|
|
|
|
for (i = 0; i < DVB_MAX_ADAPTERS; ++i) {
|
|
num = adapter_nums[i];
|
|
if (num >= 0 && num < DVB_MAX_ADAPTERS) {
|
|
/* use the one the driver asked for */
|
|
if (dvbdev_check_free_adapter_num(num))
|
|
break;
|
|
} else {
|
|
num = dvbdev_get_free_adapter_num();
|
|
break;
|
|
}
|
|
num = -1;
|
|
}
|
|
|
|
if (num < 0) {
|
|
mutex_unlock(&dvbdev_register_lock);
|
|
return -ENFILE;
|
|
}
|
|
|
|
memset (adap, 0, sizeof(struct dvb_adapter));
|
|
INIT_LIST_HEAD (&adap->device_list);
|
|
|
|
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 */
|
|
if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD)
|
|
err = -ENOTTY;
|
|
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
/* Copy results into user buffer */
|
|
switch (_IOC_DIR(cmd))
|
|
{
|
|
case _IOC_READ:
|
|
case (_IOC_WRITE | _IOC_READ):
|
|
if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
|
|
err = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
out:
|
|
kfree(mbuf);
|
|
return err;
|
|
}
|
|
|
|
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");
|