diff options
Diffstat (limited to 'drivers/media/v4l2-core')
24 files changed, 17467 insertions, 0 deletions
diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig new file mode 100644 index 000000000000..6f53337c4b4f --- /dev/null +++ b/drivers/media/v4l2-core/Kconfig @@ -0,0 +1,60 @@ +# +# Generic video config states +# + +config VIDEO_V4L2 + tristate + depends on VIDEO_DEV && VIDEO_V4L2_COMMON + default y + +config VIDEOBUF_GEN + tristate + +config VIDEOBUF_DMA_SG + depends on HAS_DMA + select VIDEOBUF_GEN + tristate + +config VIDEOBUF_VMALLOC + select VIDEOBUF_GEN + tristate + +config VIDEOBUF_DMA_CONTIG + depends on HAS_DMA + select VIDEOBUF_GEN + tristate + +config VIDEOBUF_DVB + tristate + select VIDEOBUF_GEN + +config VIDEO_TUNER + tristate + depends on MEDIA_TUNER + +config V4L2_MEM2MEM_DEV + tristate + depends on VIDEOBUF2_CORE + +config VIDEOBUF2_CORE + tristate + +config VIDEOBUF2_MEMOPS + tristate + +config VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + tristate + +config VIDEOBUF2_VMALLOC + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + tristate + +config VIDEOBUF2_DMA_SG + #depends on HAS_DMA + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + tristate + diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile new file mode 100644 index 000000000000..7319c27e256b --- /dev/null +++ b/drivers/media/v4l2-core/Makefile @@ -0,0 +1,35 @@ +# +# Makefile for the V4L2 core +# + +tuner-objs := tuner-core.o + +videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ + v4l2-event.o v4l2-ctrls.o v4l2-subdev.o +ifeq ($(CONFIG_COMPAT),y) + videodev-objs += v4l2-compat-ioctl32.o +endif + +obj-$(CONFIG_VIDEO_DEV) += videodev.o v4l2-int-device.o +obj-$(CONFIG_VIDEO_V4L2_COMMON) += v4l2-common.o + +obj-$(CONFIG_VIDEO_TUNER) += tuner.o + +obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o + +obj-$(CONFIG_VIDEOBUF_GEN) += videobuf-core.o +obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o +obj-$(CONFIG_VIDEOBUF_DMA_CONTIG) += videobuf-dma-contig.o +obj-$(CONFIG_VIDEOBUF_VMALLOC) += videobuf-vmalloc.o +obj-$(CONFIG_VIDEOBUF_DVB) += videobuf-dvb.o + +obj-$(CONFIG_VIDEOBUF2_CORE) += videobuf2-core.o +obj-$(CONFIG_VIDEOBUF2_MEMOPS) += videobuf2-memops.o +obj-$(CONFIG_VIDEOBUF2_VMALLOC) += videobuf2-vmalloc.o +obj-$(CONFIG_VIDEOBUF2_DMA_CONTIG) += videobuf2-dma-contig.o +obj-$(CONFIG_VIDEOBUF2_DMA_SG) += videobuf2-dma-sg.o + +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb/frontends +ccflags-y += -I$(srctree)/drivers/media/common/tuners + diff --git a/drivers/media/v4l2-core/tuner-core.c b/drivers/media/v4l2-core/tuner-core.c new file mode 100644 index 000000000000..b5a819af2b8c --- /dev/null +++ b/drivers/media/v4l2-core/tuner-core.c @@ -0,0 +1,1354 @@ +/* + * i2c tv tuner chip device driver + * core core, i.e. kernel interfaces, registering and so on + * + * Copyright(c) by Ralph Metzler, Gerd Knorr, Gunther Mayer + * + * Copyright(c) 2005-2011 by Mauro Carvalho Chehab + * - Added support for a separate Radio tuner + * - Major rework and cleanups at the code + * + * This driver supports many devices and the idea is to let the driver + * detect which device is present. So rather than listing all supported + * devices here, we pretend to support a single, fake device type that will + * handle both radio and analog TV tuning. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/videodev2.h> +#include <media/tuner.h> +#include <media/tuner-types.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include "mt20xx.h" +#include "tda8290.h" +#include "tea5761.h" +#include "tea5767.h" +#include "tuner-xc2028.h" +#include "tuner-simple.h" +#include "tda9887.h" +#include "xc5000.h" +#include "tda18271.h" +#include "xc4000.h" + +#define UNSET (-1U) + +#define PREFIX (t->i2c->driver->driver.name) + +/* + * Driver modprobe parameters + */ + +/* insmod options used at init time => read/only */ +static unsigned int addr; +static unsigned int no_autodetect; +static unsigned int show_i2c; + +module_param(addr, int, 0444); +module_param(no_autodetect, int, 0444); +module_param(show_i2c, int, 0444); + +/* insmod options used at runtime => read/write */ +static int tuner_debug; +static unsigned int tv_range[2] = { 44, 958 }; +static unsigned int radio_range[2] = { 65, 108 }; +static char pal[] = "--"; +static char secam[] = "--"; +static char ntsc[] = "-"; + +module_param_named(debug, tuner_debug, int, 0644); +module_param_array(tv_range, int, NULL, 0644); +module_param_array(radio_range, int, NULL, 0644); +module_param_string(pal, pal, sizeof(pal), 0644); +module_param_string(secam, secam, sizeof(secam), 0644); +module_param_string(ntsc, ntsc, sizeof(ntsc), 0644); + +/* + * Static vars + */ + +static LIST_HEAD(tuner_list); +static const struct v4l2_subdev_ops tuner_ops; + +/* + * Debug macros + */ + +#define tuner_warn(fmt, arg...) do { \ + printk(KERN_WARNING "%s %d-%04x: " fmt, PREFIX, \ + i2c_adapter_id(t->i2c->adapter), \ + t->i2c->addr, ##arg); \ + } while (0) + +#define tuner_info(fmt, arg...) do { \ + printk(KERN_INFO "%s %d-%04x: " fmt, PREFIX, \ + i2c_adapter_id(t->i2c->adapter), \ + t->i2c->addr, ##arg); \ + } while (0) + +#define tuner_err(fmt, arg...) do { \ + printk(KERN_ERR "%s %d-%04x: " fmt, PREFIX, \ + i2c_adapter_id(t->i2c->adapter), \ + t->i2c->addr, ##arg); \ + } while (0) + +#define tuner_dbg(fmt, arg...) do { \ + if (tuner_debug) \ + printk(KERN_DEBUG "%s %d-%04x: " fmt, PREFIX, \ + i2c_adapter_id(t->i2c->adapter), \ + t->i2c->addr, ##arg); \ + } while (0) + +/* + * Internal struct used inside the driver + */ + +struct tuner { + /* device */ + struct dvb_frontend fe; + struct i2c_client *i2c; + struct v4l2_subdev sd; + struct list_head list; + + /* keep track of the current settings */ + v4l2_std_id std; + unsigned int tv_freq; + unsigned int radio_freq; + unsigned int audmode; + + enum v4l2_tuner_type mode; + unsigned int mode_mask; /* Combination of allowable modes */ + + bool standby; /* Standby mode */ + + unsigned int type; /* chip type id */ + unsigned int config; + const char *name; +}; + +/* + * Function prototypes + */ + +static void set_tv_freq(struct i2c_client *c, unsigned int freq); +static void set_radio_freq(struct i2c_client *c, unsigned int freq); + +/* + * tuner attach/detach logic + */ + +/* This macro allows us to probe dynamically, avoiding static links */ +#ifdef CONFIG_MEDIA_ATTACH +#define tuner_symbol_probe(FUNCTION, ARGS...) ({ \ + int __r = -EINVAL; \ + typeof(&FUNCTION) __a = symbol_request(FUNCTION); \ + if (__a) { \ + __r = (int) __a(ARGS); \ + symbol_put(FUNCTION); \ + } else { \ + printk(KERN_ERR "TUNER: Unable to find " \ + "symbol "#FUNCTION"()\n"); \ + } \ + __r; \ +}) + +static void tuner_detach(struct dvb_frontend *fe) +{ + 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); + } +} +#else +#define tuner_symbol_probe(FUNCTION, ARGS...) ({ \ + FUNCTION(ARGS); \ +}) + +static void tuner_detach(struct dvb_frontend *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); +} +#endif + + +static inline struct tuner *to_tuner(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tuner, sd); +} + +/* + * struct analog_demod_ops callbacks + */ + +static void fe_set_params(struct dvb_frontend *fe, + struct analog_parameters *params) +{ + struct dvb_tuner_ops *fe_tuner_ops = &fe->ops.tuner_ops; + struct tuner *t = fe->analog_demod_priv; + + if (NULL == fe_tuner_ops->set_analog_params) { + tuner_warn("Tuner frontend module has no way to set freq\n"); + return; + } + fe_tuner_ops->set_analog_params(fe, params); +} + +static void fe_standby(struct dvb_frontend *fe) +{ + struct dvb_tuner_ops *fe_tuner_ops = &fe->ops.tuner_ops; + + if (fe_tuner_ops->sleep) + fe_tuner_ops->sleep(fe); +} + +static int fe_has_signal(struct dvb_frontend *fe) +{ + u16 strength = 0; + + if (fe->ops.tuner_ops.get_rf_strength) + fe->ops.tuner_ops.get_rf_strength(fe, &strength); + + return strength; +} + +static int fe_get_afc(struct dvb_frontend *fe) +{ + s32 afc = 0; + + if (fe->ops.tuner_ops.get_afc) + fe->ops.tuner_ops.get_afc(fe, &afc); + + return 0; +} + +static int fe_set_config(struct dvb_frontend *fe, void *priv_cfg) +{ + struct dvb_tuner_ops *fe_tuner_ops = &fe->ops.tuner_ops; + struct tuner *t = fe->analog_demod_priv; + + if (fe_tuner_ops->set_config) + return fe_tuner_ops->set_config(fe, priv_cfg); + + tuner_warn("Tuner frontend module has no way to set config\n"); + + return 0; +} + +static void tuner_status(struct dvb_frontend *fe); + +static struct analog_demod_ops tuner_analog_ops = { + .set_params = fe_set_params, + .standby = fe_standby, + .has_signal = fe_has_signal, + .get_afc = fe_get_afc, + .set_config = fe_set_config, + .tuner_status = tuner_status +}; + +/* + * Functions to select between radio and TV and tuner probe/remove functions + */ + +/** + * set_type - Sets the tuner type for a given device + * + * @c: i2c_client descriptoy + * @type: type of the tuner (e. g. tuner number) + * @new_mode_mask: Indicates if tuner supports TV and/or Radio + * @new_config: an optional parameter ranging from 0-255 used by + a few tuners to adjust an internal parameter, + like LNA mode + * @tuner_callback: an optional function to be called when switching + * to analog mode + * + * This function applys the tuner config to tuner specified + * by tun_setup structure. It contains several per-tuner initialization "magic" + */ +static void set_type(struct i2c_client *c, unsigned int type, + unsigned int new_mode_mask, unsigned int new_config, + int (*tuner_callback) (void *dev, int component, int cmd, int arg)) +{ + struct tuner *t = to_tuner(i2c_get_clientdata(c)); + struct dvb_tuner_ops *fe_tuner_ops = &t->fe.ops.tuner_ops; + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + unsigned char buffer[4]; + int tune_now = 1; + + if (type == UNSET || type == TUNER_ABSENT) { + tuner_dbg("tuner 0x%02x: Tuner type absent\n", c->addr); + return; + } + + t->type = type; + /* prevent invalid config values */ + t->config = new_config < 256 ? new_config : 0; + if (tuner_callback != NULL) { + tuner_dbg("defining GPIO callback\n"); + t->fe.callback = tuner_callback; + } + + /* discard private data, in case set_type() was previously called */ + tuner_detach(&t->fe); + t->fe.analog_demod_priv = NULL; + + switch (t->type) { + case TUNER_MT2032: + if (!dvb_attach(microtune_attach, + &t->fe, t->i2c->adapter, t->i2c->addr)) + goto attach_failed; + break; + case TUNER_PHILIPS_TDA8290: + { + struct tda829x_config cfg = { + .lna_cfg = t->config, + }; + if (!dvb_attach(tda829x_attach, &t->fe, t->i2c->adapter, + t->i2c->addr, &cfg)) + goto attach_failed; + break; + } + case TUNER_TEA5767: + if (!dvb_attach(tea5767_attach, &t->fe, + t->i2c->adapter, t->i2c->addr)) + goto attach_failed; + t->mode_mask = T_RADIO; + break; + case TUNER_TEA5761: + if (!dvb_attach(tea5761_attach, &t->fe, + t->i2c->adapter, t->i2c->addr)) + goto attach_failed; + t->mode_mask = T_RADIO; + break; + case TUNER_PHILIPS_FMD1216ME_MK3: + case TUNER_PHILIPS_FMD1216MEX_MK3: + buffer[0] = 0x0b; + buffer[1] = 0xdc; + buffer[2] = 0x9c; + buffer[3] = 0x60; + i2c_master_send(c, buffer, 4); + mdelay(1); + buffer[2] = 0x86; + buffer[3] = 0x54; + i2c_master_send(c, buffer, 4); + if (!dvb_attach(simple_tuner_attach, &t->fe, + t->i2c->adapter, t->i2c->addr, t->type)) + goto attach_failed; + break; + case TUNER_PHILIPS_TD1316: + buffer[0] = 0x0b; + buffer[1] = 0xdc; + buffer[2] = 0x86; + buffer[3] = 0xa4; + i2c_master_send(c, buffer, 4); + if (!dvb_attach(simple_tuner_attach, &t->fe, + t->i2c->adapter, t->i2c->addr, t->type)) + goto attach_failed; + break; + case TUNER_XC2028: + { + struct xc2028_config cfg = { + .i2c_adap = t->i2c->adapter, + .i2c_addr = t->i2c->addr, + }; + if (!dvb_attach(xc2028_attach, &t->fe, &cfg)) + goto attach_failed; + tune_now = 0; + break; + } + case TUNER_TDA9887: + if (!dvb_attach(tda9887_attach, + &t->fe, t->i2c->adapter, t->i2c->addr)) + goto attach_failed; + break; + case TUNER_XC5000: + { + struct xc5000_config xc5000_cfg = { + .i2c_address = t->i2c->addr, + /* if_khz will be set at dvb_attach() */ + .if_khz = 0, + }; + + if (!dvb_attach(xc5000_attach, + &t->fe, t->i2c->adapter, &xc5000_cfg)) + goto attach_failed; + tune_now = 0; + break; + } + case TUNER_XC5000C: + { + struct xc5000_config xc5000c_cfg = { + .i2c_address = t->i2c->addr, + /* if_khz will be set at dvb_attach() */ + .if_khz = 0, + .chip_id = XC5000C, + }; + + if (!dvb_attach(xc5000_attach, + &t->fe, t->i2c->adapter, &xc5000c_cfg)) + goto attach_failed; + tune_now = 0; + break; + } + case TUNER_NXP_TDA18271: + { + struct tda18271_config cfg = { + .config = t->config, + .small_i2c = TDA18271_03_BYTE_CHUNK_INIT, + }; + + if (!dvb_attach(tda18271_attach, &t->fe, t->i2c->addr, + t->i2c->adapter, &cfg)) + goto attach_failed; + tune_now = 0; + break; + } + case TUNER_XC4000: + { + struct xc4000_config xc4000_cfg = { + .i2c_address = t->i2c->addr, + /* FIXME: the correct parameters will be set */ + /* only when the digital dvb_attach() occurs */ + .default_pm = 0, + .dvb_amplitude = 0, + .set_smoothedcvbs = 0, + .if_khz = 0 + }; + if (!dvb_attach(xc4000_attach, + &t->fe, t->i2c->adapter, &xc4000_cfg)) + goto attach_failed; + tune_now = 0; + break; + } + default: + if (!dvb_attach(simple_tuner_attach, &t->fe, + t->i2c->adapter, t->i2c->addr, t->type)) + goto attach_failed; + + break; + } + + if ((NULL == analog_ops->set_params) && + (fe_tuner_ops->set_analog_params)) { + + t->name = fe_tuner_ops->info.name; + + t->fe.analog_demod_priv = t; + memcpy(analog_ops, &tuner_analog_ops, + sizeof(struct analog_demod_ops)); + + } else { + t->name = analog_ops->info.name; + } + + tuner_dbg("type set to %s\n", t->name); + + t->mode_mask = new_mode_mask; + + /* Some tuners require more initialization setup before use, + such as firmware download or device calibration. + trying to set a frequency here will just fail + FIXME: better to move set_freq to the tuner code. This is needed + on analog tuners for PLL to properly work + */ + if (tune_now) { + if (V4L2_TUNER_RADIO == t->mode) + set_radio_freq(c, t->radio_freq); + else + set_tv_freq(c, t->tv_freq); + } + + tuner_dbg("%s %s I2C addr 0x%02x with type %d used for 0x%02x\n", + c->adapter->name, c->driver->driver.name, c->addr << 1, type, + t->mode_mask); + return; + +attach_failed: + tuner_dbg("Tuner attach for type = %d failed.\n", t->type); + t->type = TUNER_ABSENT; + + return; +} + +/** + * tuner_s_type_addr - Sets the tuner type for a device + * + * @sd: subdev descriptor + * @tun_setup: type to be associated to a given tuner i2c address + * + * This function applys the tuner config to tuner specified + * by tun_setup structure. + * If tuner I2C address is UNSET, then it will only set the device + * if the tuner supports the mode specified in the call. + * If the address is specified, the change will be applied only if + * tuner I2C address matches. + * The call can change the tuner number and the tuner mode. + */ +static int tuner_s_type_addr(struct v4l2_subdev *sd, + struct tuner_setup *tun_setup) +{ + struct tuner *t = to_tuner(sd); + struct i2c_client *c = v4l2_get_subdevdata(sd); + + tuner_dbg("Calling set_type_addr for type=%d, addr=0x%02x, mode=0x%02x, config=0x%02x\n", + tun_setup->type, + tun_setup->addr, + tun_setup->mode_mask, + tun_setup->config); + + if ((t->type == UNSET && ((tun_setup->addr == ADDR_UNSET) && + (t->mode_mask & tun_setup->mode_mask))) || + (tun_setup->addr == c->addr)) { + set_type(c, tun_setup->type, tun_setup->mode_mask, + tun_setup->config, tun_setup->tuner_callback); + } else + tuner_dbg("set addr discarded for type %i, mask %x. " + "Asked to change tuner at addr 0x%02x, with mask %x\n", + t->type, t->mode_mask, + tun_setup->addr, tun_setup->mode_mask); + + return 0; +} + +/** + * tuner_s_config - Sets tuner configuration + * + * @sd: subdev descriptor + * @cfg: tuner configuration + * + * Calls tuner set_config() private function to set some tuner-internal + * parameters + */ +static int tuner_s_config(struct v4l2_subdev *sd, + const struct v4l2_priv_tun_config *cfg) +{ + struct tuner *t = to_tuner(sd); + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + + if (t->type != cfg->tuner) + return 0; + + if (analog_ops->set_config) { + analog_ops->set_config(&t->fe, cfg->priv); + return 0; + } + + tuner_dbg("Tuner frontend module has no way to set config\n"); + return 0; +} + +/** + * tuner_lookup - Seek for tuner adapters + * + * @adap: i2c_adapter struct + * @radio: pointer to be filled if the adapter is radio + * @tv: pointer to be filled if the adapter is TV + * + * Search for existing radio and/or TV tuners on the given I2C adapter, + * discarding demod-only adapters (tda9887). + * + * Note that when this function is called from tuner_probe you can be + * certain no other devices will be added/deleted at the same time, I2C + * core protects against that. + */ +static void tuner_lookup(struct i2c_adapter *adap, + struct tuner **radio, struct tuner **tv) +{ + struct tuner *pos; + + *radio = NULL; + *tv = NULL; + + list_for_each_entry(pos, &tuner_list, list) { + int mode_mask; + + if (pos->i2c->adapter != adap || + strcmp(pos->i2c->driver->driver.name, "tuner")) + continue; + + mode_mask = pos->mode_mask; + if (*radio == NULL && mode_mask == T_RADIO) + *radio = pos; + /* Note: currently TDA9887 is the only demod-only + device. If other devices appear then we need to + make this test more general. */ + else if (*tv == NULL && pos->type != TUNER_TDA9887 && + (pos->mode_mask & T_ANALOG_TV)) + *tv = pos; + } +} + +/** + *tuner_probe - Probes the existing tuners on an I2C bus + * + * @client: i2c_client descriptor + * @id: not used + * + * This routine probes for tuners at the expected I2C addresses. On most + * cases, if a device answers to a given I2C address, it assumes that the + * device is a tuner. On a few cases, however, an additional logic is needed + * to double check if the device is really a tuner, or to identify the tuner + * type, like on tea5767/5761 devices. + * + * During client attach, set_type is called by adapter's attach_inform callback. + * set_type must then be completed by tuner_probe. + */ +static int tuner_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tuner *t; + struct tuner *radio; + struct tuner *tv; + + t = kzalloc(sizeof(struct tuner), GFP_KERNEL); + if (NULL == t) + return -ENOMEM; + v4l2_i2c_subdev_init(&t->sd, client, &tuner_ops); + t->i2c = client; + t->name = "(tuner unset)"; + t->type = UNSET; + t->audmode = V4L2_TUNER_MODE_STEREO; + t->standby = 1; + t->radio_freq = 87.5 * 16000; /* Initial freq range */ + t->tv_freq = 400 * 16; /* Sets freq to VHF High - needed for some PLL's to properly start */ + + if (show_i2c) { + unsigned char buffer[16]; + int i, rc; + + memset(buffer, 0, sizeof(buffer)); + rc = i2c_master_recv(client, buffer, sizeof(buffer)); + tuner_info("I2C RECV = "); + for (i = 0; i < rc; i++) + printk(KERN_CONT "%02x ", buffer[i]); + printk("\n"); + } + + /* autodetection code based on the i2c addr */ + if (!no_autodetect) { + switch (client->addr) { + case 0x10: + if (tuner_symbol_probe(tea5761_autodetection, + t->i2c->adapter, + t->i2c->addr) >= 0) { + t->type = TUNER_TEA5761; + t->mode_mask = T_RADIO; + tuner_lookup(t->i2c->adapter, &radio, &tv); + if (tv) + tv->mode_mask &= ~T_RADIO; + + goto register_client; + } + kfree(t); + return -ENODEV; + case 0x42: + case 0x43: + case 0x4a: + case 0x4b: + /* If chip is not tda8290, don't register. + since it can be tda9887*/ + if (tuner_symbol_probe(tda829x_probe, t->i2c->adapter, + t->i2c->addr) >= 0) { + tuner_dbg("tda829x detected\n"); + } else { + /* Default is being tda9887 */ + t->type = TUNER_TDA9887; + t->mode_mask = T_RADIO | T_ANALOG_TV; + goto register_client; + } + break; + case 0x60: + if (tuner_symbol_probe(tea5767_autodetection, + t->i2c->adapter, t->i2c->addr) + >= 0) { + t->type = TUNER_TEA5767; + t->mode_mask = T_RADIO; + /* Sets freq to FM range */ + tuner_lookup(t->i2c->adapter, &radio, &tv); + if (tv) + tv->mode_mask &= ~T_RADIO; + + goto register_client; + } + break; + } + } + + /* Initializes only the first TV tuner on this adapter. Why only the + first? Because there are some devices (notably the ones with TI + tuners) that have more than one i2c address for the *same* device. + Experience shows that, except for just one case, the first + address is the right one. The exception is a Russian tuner + (ACORP_Y878F). So, the desired behavior is just to enable the + first found TV tuner. */ + tuner_lookup(t->i2c->adapter, &radio, &tv); + if (tv == NULL) { + t->mode_mask = T_ANALOG_TV; + if (radio == NULL) + t->mode_mask |= T_RADIO; + tuner_dbg("Setting mode_mask to 0x%02x\n", t->mode_mask); + } + + /* Should be just before return */ +register_client: + /* Sets a default mode */ + if (t->mode_mask & T_ANALOG_TV) + t->mode = V4L2_TUNER_ANALOG_TV; + else + t->mode = V4L2_TUNER_RADIO; + set_type(client, t->type, t->mode_mask, t->config, t->fe.callback); + list_add_tail(&t->list, &tuner_list); + + tuner_info("Tuner %d found with type(s)%s%s.\n", + t->type, + t->mode_mask & T_RADIO ? " Radio" : "", + t->mode_mask & T_ANALOG_TV ? " TV" : ""); + return 0; +} + +/** + * tuner_remove - detaches a tuner + * + * @client: i2c_client descriptor + */ + +static int tuner_remove(struct i2c_client *client) +{ + struct tuner *t = to_tuner(i2c_get_clientdata(client)); + + v4l2_device_unregister_subdev(&t->sd); + tuner_detach(&t->fe); + t->fe.analog_demod_priv = NULL; + + list_del(&t->list); + kfree(t); + return 0; +} + +/* + * Functions to switch between Radio and TV + * + * A few cards have a separate I2C tuner for radio. Those routines + * take care of switching between TV/Radio mode, filtering only the + * commands that apply to the Radio or TV tuner. + */ + +/** + * check_mode - Verify if tuner supports the requested mode + * @t: a pointer to the module's internal struct_tuner + * + * This function checks if the tuner is capable of tuning analog TV, + * digital TV or radio, depending on what the caller wants. If the + * tuner can't support that mode, it returns -EINVAL. Otherwise, it + * returns 0. + * This function is needed for boards that have a separate tuner for + * radio (like devices with tea5767). + * NOTE: mt20xx uses V4L2_TUNER_DIGITAL_TV and calls set_tv_freq to + * select a TV frequency. So, t_mode = T_ANALOG_TV could actually + * be used to represent a Digital TV too. + */ +static inline int check_mode(struct tuner *t, enum v4l2_tuner_type mode) +{ + int t_mode; + if (mode == V4L2_TUNER_RADIO) + t_mode = T_RADIO; + else + t_mode = T_ANALOG_TV; + + if ((t_mode & t->mode_mask) == 0) + return -EINVAL; + + return 0; +} + +/** + * set_mode - Switch tuner to other mode. + * @t: a pointer to the module's internal struct_tuner + * @mode: enum v4l2_type (radio or TV) + * + * If tuner doesn't support the needed mode (radio or TV), prints a + * debug message and returns -EINVAL, changing its state to standby. + * Otherwise, changes the mode and returns 0. + */ +static int set_mode(struct tuner *t, enum v4l2_tuner_type mode) +{ + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + + if (mode != t->mode) { + if (check_mode(t, mode) == -EINVAL) { + tuner_dbg("Tuner doesn't support mode %d. " + "Putting tuner to sleep\n", mode); + t->standby = true; + if (analog_ops->standby) + analog_ops->standby(&t->fe); + return -EINVAL; + } + t->mode = mode; + tuner_dbg("Changing to mode %d\n", mode); + } + return 0; +} + +/** + * set_freq - Set the tuner to the desired frequency. + * @t: a pointer to the module's internal struct_tuner + * @freq: frequency to set (0 means to use the current frequency) + */ +static void set_freq(struct tuner *t, unsigned int freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(&t->sd); + + if (t->mode == V4L2_TUNER_RADIO) { + if (!freq) + freq = t->radio_freq; + set_radio_freq(client, freq); + } else { + if (!freq) + freq = t->tv_freq; + set_tv_freq(client, freq); + } +} + +/* + * Functions that are specific for TV mode + */ + +/** + * set_tv_freq - Set tuner frequency, freq in Units of 62.5 kHz = 1/16MHz + * + * @c: i2c_client descriptor + * @freq: frequency + */ +static void set_tv_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = to_tuner(i2c_get_clientdata(c)); + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + + struct analog_parameters params = { + .mode = t->mode, + .audmode = t->audmode, + .std = t->std + }; + + if (t->type == UNSET) { + tuner_warn("tuner type not set\n"); + return; + } + if (NULL == analog_ops->set_params) { + tuner_warn("Tuner has no way to set tv freq\n"); + return; + } + if (freq < tv_range[0] * 16 || freq > tv_range[1] * 16) { + tuner_dbg("TV freq (%d.%02d) out of range (%d-%d)\n", + freq / 16, freq % 16 * 100 / 16, tv_range[0], + tv_range[1]); + /* V4L2 spec: if the freq is not possible then the closest + possible value should be selected */ + if (freq < tv_range[0] * 16) + freq = tv_range[0] * 16; + else + freq = tv_range[1] * 16; + } + params.frequency = freq; + tuner_dbg("tv freq set to %d.%02d\n", + freq / 16, freq % 16 * 100 / 16); + t->tv_freq = freq; + t->standby = false; + + analog_ops->set_params(&t->fe, ¶ms); +} + +/** + * tuner_fixup_std - force a given video standard variant + * + * @t: tuner internal struct + * @std: TV standard + * + * A few devices or drivers have problem to detect some standard variations. + * On other operational systems, the drivers generally have a per-country + * code, and some logic to apply per-country hacks. V4L2 API doesn't provide + * such hacks. Instead, it relies on a proper video standard selection from + * the userspace application. However, as some apps are buggy, not allowing + * to distinguish all video standard variations, a modprobe parameter can + * be used to force a video standard match. + */ +static v4l2_std_id tuner_fixup_std(struct tuner *t, v4l2_std_id std) +{ + if (pal[0] != '-' && (std & V4L2_STD_PAL) == V4L2_STD_PAL) { + switch (pal[0]) { + case '6': + return V4L2_STD_PAL_60; + case 'b': + case 'B': + case 'g': + case 'G': + return V4L2_STD_PAL_BG; + case 'i': + case 'I': + return V4L2_STD_PAL_I; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_PAL_DK; + case 'M': + case 'm': + return V4L2_STD_PAL_M; + case 'N': + case 'n': + if (pal[1] == 'c' || pal[1] == 'C') + return V4L2_STD_PAL_Nc; + return V4L2_STD_PAL_N; + default: + tuner_warn("pal= argument not recognised\n"); + break; + } + } + if (secam[0] != '-' && (std & V4L2_STD_SECAM) == V4L2_STD_SECAM) { + switch (secam[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + return V4L2_STD_SECAM_B | + V4L2_STD_SECAM_G | + V4L2_STD_SECAM_H; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_SECAM_DK; + case 'l': + case 'L': + if ((secam[1] == 'C') || (secam[1] == 'c')) + return V4L2_STD_SECAM_LC; + return V4L2_STD_SECAM_L; + default: + tuner_warn("secam= argument not recognised\n"); + break; + } + } + + if (ntsc[0] != '-' && (std & V4L2_STD_NTSC) == V4L2_STD_NTSC) { + switch (ntsc[0]) { + case 'm': + case 'M': + return V4L2_STD_NTSC_M; + case 'j': + case 'J': + return V4L2_STD_NTSC_M_JP; + case 'k': + case 'K': + return V4L2_STD_NTSC_M_KR; + default: + tuner_info("ntsc= argument not recognised\n"); + break; + } + } + return std; +} + +/* + * Functions that are specific for Radio mode + */ + +/** + * set_radio_freq - Set tuner frequency, freq in Units of 62.5 Hz = 1/16kHz + * + * @c: i2c_client descriptor + * @freq: frequency + */ +static void set_radio_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = to_tuner(i2c_get_clientdata(c)); + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + + struct analog_parameters params = { + .mode = t->mode, + .audmode = t->audmode, + .std = t->std + }; + + if (t->type == UNSET) { + tuner_warn("tuner type not set\n"); + return; + } + if (NULL == analog_ops->set_params) { + tuner_warn("tuner has no way to set radio frequency\n"); + return; + } + if (freq < radio_range[0] * 16000 || freq > radio_range[1] * 16000) { + tuner_dbg("radio freq (%d.%02d) out of range (%d-%d)\n", + freq / 16000, freq % 16000 * 100 / 16000, + radio_range[0], radio_range[1]); + /* V4L2 spec: if the freq is not possible then the closest + possible value should be selected */ + if (freq < radio_range[0] * 16000) + freq = radio_range[0] * 16000; + else + freq = radio_range[1] * 16000; + } + params.frequency = freq; + tuner_dbg("radio freq set to %d.%02d\n", + freq / 16000, freq % 16000 * 100 / 16000); + t->radio_freq = freq; + t->standby = false; + + analog_ops->set_params(&t->fe, ¶ms); +} + +/* + * Debug function for reporting tuner status to userspace + */ + +/** + * tuner_status - Dumps the current tuner status at dmesg + * @fe: pointer to struct dvb_frontend + * + * This callback is used only for driver debug purposes, answering to + * VIDIOC_LOG_STATUS. No changes should happen on this call. + */ +static void tuner_status(struct dvb_frontend *fe) +{ + struct tuner *t = fe->analog_demod_priv; + unsigned long freq, freq_fraction; + struct dvb_tuner_ops *fe_tuner_ops = &fe->ops.tuner_ops; + struct analog_demod_ops *analog_ops = &fe->ops.analog_ops; + const char *p; + + switch (t->mode) { + case V4L2_TUNER_RADIO: + p = "radio"; + break; + case V4L2_TUNER_DIGITAL_TV: /* Used by mt20xx */ + p = "digital TV"; + break; + case V4L2_TUNER_ANALOG_TV: + default: + p = "analog TV"; + break; + } + if (t->mode == V4L2_TUNER_RADIO) { + freq = t->radio_freq / 16000; + freq_fraction = (t->radio_freq % 16000) * 100 / 16000; + } else { + freq = t->tv_freq / 16; + freq_fraction = (t->tv_freq % 16) * 100 / 16; + } + tuner_info("Tuner mode: %s%s\n", p, + t->standby ? " on standby mode" : ""); + tuner_info("Frequency: %lu.%02lu MHz\n", freq, freq_fraction); + tuner_info("Standard: 0x%08lx\n", (unsigned long)t->std); + if (t->mode != V4L2_TUNER_RADIO) + return; + if (fe_tuner_ops->get_status) { + u32 tuner_status; + + fe_tuner_ops->get_status(&t->fe, &tuner_status); + if (tuner_status & TUNER_STATUS_LOCKED) + tuner_info("Tuner is locked.\n"); + if (tuner_status & TUNER_STATUS_STEREO) + tuner_info("Stereo: yes\n"); + } + if (analog_ops->has_signal) + tuner_info("Signal strength: %d\n", + analog_ops->has_signal(fe)); +} + +/* + * Function to splicitly change mode to radio. Probably not needed anymore + */ + +static int tuner_s_radio(struct v4l2_subdev *sd) +{ + struct tuner *t = to_tuner(sd); + + if (set_mode(t, V4L2_TUNER_RADIO) == 0) + set_freq(t, 0); + return 0; +} + +/* + * Tuner callbacks to handle userspace ioctl's + */ + +/** + * tuner_s_power - controls the power state of the tuner + * @sd: pointer to struct v4l2_subdev + * @on: a zero value puts the tuner to sleep, non-zero wakes it up + */ +static int tuner_s_power(struct v4l2_subdev *sd, int on) +{ + struct tuner *t = to_tuner(sd); + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + + if (on) { + if (t->standby && set_mode(t, t->mode) == 0) { + tuner_dbg("Waking up tuner\n"); + set_freq(t, 0); + } + return 0; + } + + tuner_dbg("Putting tuner to sleep\n"); + t->standby = true; + if (analog_ops->standby) + analog_ops->standby(&t->fe); + return 0; +} + +static int tuner_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct tuner *t = to_tuner(sd); + + if (set_mode(t, V4L2_TUNER_ANALOG_TV)) + return 0; + + t->std = tuner_fixup_std(t, std); + if (t->std != std) + tuner_dbg("Fixup standard %llx to %llx\n", std, t->std); + set_freq(t, 0); + return 0; +} + +static int tuner_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) +{ + struct tuner *t = to_tuner(sd); + + if (set_mode(t, f->type) == 0) + set_freq(t, f->frequency); + return 0; +} + +/** + * tuner_g_frequency - Get the tuned frequency for the tuner + * @sd: pointer to struct v4l2_subdev + * @f: pointer to struct v4l2_frequency + * + * At return, the structure f will be filled with tuner frequency + * if the tuner matches the f->type. + * Note: f->type should be initialized before calling it. + * This is done by either video_ioctl2 or by the bridge driver. + */ +static int tuner_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) +{ + struct tuner *t = to_tuner(sd); + struct dvb_tuner_ops *fe_tuner_ops = &t->fe.ops.tuner_ops; + + if (check_mode(t, f->type) == -EINVAL) + return 0; + if (f->type == t->mode && fe_tuner_ops->get_frequency && !t->standby) { + u32 abs_freq; + + fe_tuner_ops->get_frequency(&t->fe, &abs_freq); + f->frequency = (V4L2_TUNER_RADIO == t->mode) ? + DIV_ROUND_CLOSEST(abs_freq * 2, 125) : + DIV_ROUND_CLOSEST(abs_freq, 62500); + } else { + f->frequency = (V4L2_TUNER_RADIO == f->type) ? + t->radio_freq : t->tv_freq; + } + return 0; +} + +/** + * tuner_g_tuner - Fill in tuner information + * @sd: pointer to struct v4l2_subdev + * @vt: pointer to struct v4l2_tuner + * + * At return, the structure vt will be filled with tuner information + * if the tuner matches vt->type. + * Note: vt->type should be initialized before calling it. + * This is done by either video_ioctl2 or by the bridge driver. + */ +static int tuner_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct tuner *t = to_tuner(sd); + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + struct dvb_tuner_ops *fe_tuner_ops = &t->fe.ops.tuner_ops; + + if (check_mode(t, vt->type) == -EINVAL) + return 0; + if (vt->type == t->mode && analog_ops->get_afc) + vt->afc = analog_ops->get_afc(&t->fe); + if (analog_ops->has_signal) + vt->signal = analog_ops->has_signal(&t->fe); + if (vt->type != V4L2_TUNER_RADIO) { + vt->capability |= V4L2_TUNER_CAP_NORM; + vt->rangelow = tv_range[0] * 16; + vt->rangehigh = tv_range[1] * 16; + return 0; + } + + /* radio mode */ + if (vt->type == t->mode) { + vt->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + if (fe_tuner_ops->get_status) { + u32 tuner_status; + + fe_tuner_ops->get_status(&t->fe, &tuner_status); + vt->rxsubchans = + (tuner_status & TUNER_STATUS_STEREO) ? + V4L2_TUNER_SUB_STEREO : + V4L2_TUNER_SUB_MONO; + } + vt->audmode = t->audmode; + } + vt->capability |= V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; + vt->rangelow = radio_range[0] * 16000; + vt->rangehigh = radio_range[1] * 16000; + + return 0; +} + +/** + * tuner_s_tuner - Set the tuner's audio mode + * @sd: pointer to struct v4l2_subdev + * @vt: pointer to struct v4l2_tuner + * + * Sets the audio mode if the tuner matches vt->type. + * Note: vt->type should be initialized before calling it. + * This is done by either video_ioctl2 or by the bridge driver. + */ +static int tuner_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct tuner *t = to_tuner(sd); + + if (set_mode(t, vt->type)) + return 0; + + if (t->mode == V4L2_TUNER_RADIO) + t->audmode = vt->audmode; + set_freq(t, 0); + + return 0; +} + +static int tuner_log_status(struct v4l2_subdev *sd) +{ + struct tuner *t = to_tuner(sd); + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + + if (analog_ops->tuner_status) + analog_ops->tuner_status(&t->fe); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tuner_suspend(struct device *dev) +{ + struct i2c_client *c = to_i2c_client(dev); + struct tuner *t = to_tuner(i2c_get_clientdata(c)); + struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops; + + tuner_dbg("suspend\n"); + + if (!t->standby && analog_ops->standby) + analog_ops->standby(&t->fe); + + return 0; +} + +static int tuner_resume(struct device *dev) +{ + struct i2c_client *c = to_i2c_client(dev); + struct tuner *t = to_tuner(i2c_get_clientdata(c)); + + tuner_dbg("resume\n"); + + if (!t->standby) + if (set_mode(t, t->mode) == 0) + set_freq(t, 0); + + return 0; +} +#endif + +static int tuner_command(struct i2c_client *client, unsigned cmd, void *arg) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + /* TUNER_SET_CONFIG is still called by tuner-simple.c, so we have + to handle it here. + There must be a better way of doing this... */ + switch (cmd) { + case TUNER_SET_CONFIG: + return tuner_s_config(sd, arg); + } + return -ENOIOCTLCMD; +} + +/* + * Callback structs + */ + +static const struct v4l2_subdev_core_ops tuner_core_ops = { + .log_status = tuner_log_status, + .s_std = tuner_s_std, + .s_power = tuner_s_power, +}; + +static const struct v4l2_subdev_tuner_ops tuner_tuner_ops = { + .s_radio = tuner_s_radio, + .g_tuner = tuner_g_tuner, + .s_tuner = tuner_s_tuner, + .s_frequency = tuner_s_frequency, + .g_frequency = tuner_g_frequency, + .s_type_addr = tuner_s_type_addr, + .s_config = tuner_s_config, +}; + +static const struct v4l2_subdev_ops tuner_ops = { + .core = &tuner_core_ops, + .tuner = &tuner_tuner_ops, +}; + +/* + * I2C structs and module init functions + */ + +static const struct dev_pm_ops tuner_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tuner_suspend, tuner_resume) +}; + +static const struct i2c_device_id tuner_id[] = { + { "tuner", }, /* autodetect */ + { } +}; +MODULE_DEVICE_TABLE(i2c, tuner_id); + +static struct i2c_driver tuner_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "tuner", + .pm = &tuner_pm_ops, + }, + .probe = tuner_probe, + .remove = tuner_remove, + .command = tuner_command, + .id_table = tuner_id, +}; + +module_i2c_driver(tuner_driver); + +MODULE_DESCRIPTION("device driver for various TV and TV+FM radio tuners"); +MODULE_AUTHOR("Ralph Metzler, Gerd Knorr, Gunther Mayer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c new file mode 100644 index 000000000000..105f88cdb9d6 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -0,0 +1,623 @@ +/* + * Video for Linux Two + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * This file replaces the videodev.c file that comes with the + * regular kernel distribution. + * + * 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. + * + * Author: Bill Dirks <bill@thedirks.org> + * based on code by Alan Cox, <alan@cymru.net> + * + */ + +/* + * Video capture interface for Linux + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * 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. + * + * Author: Alan Cox, <alan@lxorguk.ukuu.org.uk> + * + * Fixes: + */ + +/* + * Video4linux 1/2 integration by Justin Schoeman + * <justin@suntiger.ee.up.ac.za> + * 2.4 PROCFS support ported from 2.4 kernels by + * Iñaki GarcÃa Etxebarria <garetxe@euskalnet.net> + * Makefile fix by "W. Michael Petullo" <mike@flyn.org> + * 2.4 devfs support ported from 2.4 kernels by + * Dan Merillat <dan@merillat.org> + * Added Gerd Knorrs v4l1 enhancements (Justin Schoeman) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#if defined(CONFIG_SPI) +#include <linux/spi/spi.h> +#endif +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/div64.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-chip-ident.h> + +#include <linux/videodev2.h> + +MODULE_AUTHOR("Bill Dirks, Justin Schoeman, Gerd Knorr"); +MODULE_DESCRIPTION("misc helper functions for v4l2 device drivers"); +MODULE_LICENSE("GPL"); + +/* + * + * V 4 L 2 D R I V E R H E L P E R A P I + * + */ + +/* + * Video Standard Operations (contributed by Michael Schimek) + */ + +/* Helper functions for control handling */ + +/* Check for correctness of the ctrl's value based on the data from + struct v4l2_queryctrl and the available menu items. Note that + menu_items may be NULL, in that case it is ignored. */ +int v4l2_ctrl_check(struct v4l2_ext_control *ctrl, struct v4l2_queryctrl *qctrl, + const char * const *menu_items) +{ + if (qctrl->flags & V4L2_CTRL_FLAG_DISABLED) + return -EINVAL; + if (qctrl->flags & V4L2_CTRL_FLAG_GRABBED) + return -EBUSY; + if (qctrl->type == V4L2_CTRL_TYPE_STRING) + return 0; + if (qctrl->type == V4L2_CTRL_TYPE_BUTTON || + qctrl->type == V4L2_CTRL_TYPE_INTEGER64 || + qctrl->type == V4L2_CTRL_TYPE_CTRL_CLASS) + return 0; + if (ctrl->value < qctrl->minimum || ctrl->value > qctrl->maximum) + return -ERANGE; + if (qctrl->type == V4L2_CTRL_TYPE_MENU && menu_items != NULL) { + if (menu_items[ctrl->value] == NULL || + menu_items[ctrl->value][0] == '\0') + return -EINVAL; + } + if (qctrl->type == V4L2_CTRL_TYPE_BITMASK && + (ctrl->value & ~qctrl->maximum)) + return -ERANGE; + return 0; +} +EXPORT_SYMBOL(v4l2_ctrl_check); + +/* Fill in a struct v4l2_queryctrl */ +int v4l2_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 max, s32 step, s32 def) +{ + const char *name; + + v4l2_ctrl_fill(qctrl->id, &name, &qctrl->type, + &min, &max, &step, &def, &qctrl->flags); + + if (name == NULL) + return -EINVAL; + + qctrl->minimum = min; + qctrl->maximum = max; + qctrl->step = step; + qctrl->default_value = def; + qctrl->reserved[0] = qctrl->reserved[1] = 0; + strlcpy(qctrl->name, name, sizeof(qctrl->name)); + return 0; +} +EXPORT_SYMBOL(v4l2_ctrl_query_fill); + +/* Fill in a struct v4l2_querymenu based on the struct v4l2_queryctrl and + the menu. The qctrl pointer may be NULL, in which case it is ignored. + If menu_items is NULL, then the menu items are retrieved using + v4l2_ctrl_get_menu. */ +int v4l2_ctrl_query_menu(struct v4l2_querymenu *qmenu, struct v4l2_queryctrl *qctrl, + const char * const *menu_items) +{ + int i; + + qmenu->reserved = 0; + if (menu_items == NULL) + menu_items = v4l2_ctrl_get_menu(qmenu->id); + if (menu_items == NULL || + (qctrl && (qmenu->index < qctrl->minimum || qmenu->index > qctrl->maximum))) + return -EINVAL; + for (i = 0; i < qmenu->index && menu_items[i]; i++) ; + if (menu_items[i] == NULL || menu_items[i][0] == '\0') + return -EINVAL; + strlcpy(qmenu->name, menu_items[qmenu->index], sizeof(qmenu->name)); + return 0; +} +EXPORT_SYMBOL(v4l2_ctrl_query_menu); + +/* Fill in a struct v4l2_querymenu based on the specified array of valid + menu items (terminated by V4L2_CTRL_MENU_IDS_END). + Use this if there are 'holes' in the list of valid menu items. */ +int v4l2_ctrl_query_menu_valid_items(struct v4l2_querymenu *qmenu, const u32 *ids) +{ + const char * const *menu_items = v4l2_ctrl_get_menu(qmenu->id); + + qmenu->reserved = 0; + if (menu_items == NULL || ids == NULL) + return -EINVAL; + while (*ids != V4L2_CTRL_MENU_IDS_END) { + if (*ids++ == qmenu->index) { + strlcpy(qmenu->name, menu_items[qmenu->index], + sizeof(qmenu->name)); + return 0; + } + } + return -EINVAL; +} +EXPORT_SYMBOL(v4l2_ctrl_query_menu_valid_items); + +/* ctrl_classes points to an array of u32 pointers, the last element is + a NULL pointer. Each u32 array is a 0-terminated array of control IDs. + Each array must be sorted low to high and belong to the same control + class. The array of u32 pointers must also be sorted, from low class IDs + to high class IDs. + + This function returns the first ID that follows after the given ID. + When no more controls are available 0 is returned. */ +u32 v4l2_ctrl_next(const u32 * const * ctrl_classes, u32 id) +{ + u32 ctrl_class = V4L2_CTRL_ID2CLASS(id); + const u32 *pctrl; + + if (ctrl_classes == NULL) + return 0; + + /* if no query is desired, then check if the ID is part of ctrl_classes */ + if ((id & V4L2_CTRL_FLAG_NEXT_CTRL) == 0) { + /* find class */ + while (*ctrl_classes && V4L2_CTRL_ID2CLASS(**ctrl_classes) != ctrl_class) + ctrl_classes++; + if (*ctrl_classes == NULL) + return 0; + pctrl = *ctrl_classes; + /* find control ID */ + while (*pctrl && *pctrl != id) pctrl++; + return *pctrl ? id : 0; + } + id &= V4L2_CTRL_ID_MASK; + id++; /* select next control */ + /* find first class that matches (or is greater than) the class of + the ID */ + while (*ctrl_classes && V4L2_CTRL_ID2CLASS(**ctrl_classes) < ctrl_class) + ctrl_classes++; + /* no more classes */ + if (*ctrl_classes == NULL) + return 0; + pctrl = *ctrl_classes; + /* find first ctrl within the class that is >= ID */ + while (*pctrl && *pctrl < id) pctrl++; + if (*pctrl) + return *pctrl; + /* we are at the end of the controls of the current class. */ + /* continue with next class if available */ + ctrl_classes++; + if (*ctrl_classes == NULL) + return 0; + return **ctrl_classes; +} +EXPORT_SYMBOL(v4l2_ctrl_next); + +int v4l2_chip_match_host(const struct v4l2_dbg_match *match) +{ + switch (match->type) { + case V4L2_CHIP_MATCH_HOST: + return match->addr == 0; + default: + return 0; + } +} +EXPORT_SYMBOL(v4l2_chip_match_host); + +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) +int v4l2_chip_match_i2c_client(struct i2c_client *c, const struct v4l2_dbg_match *match) +{ + int len; + + if (c == NULL || match == NULL) + return 0; + + switch (match->type) { + case V4L2_CHIP_MATCH_I2C_DRIVER: + if (c->driver == NULL || c->driver->driver.name == NULL) + return 0; + len = strlen(c->driver->driver.name); + /* legacy drivers have a ' suffix, don't try to match that */ + if (len && c->driver->driver.name[len - 1] == '\'') + len--; + return len && !strncmp(c->driver->driver.name, match->name, len); + case V4L2_CHIP_MATCH_I2C_ADDR: + return c->addr == match->addr; + default: + return 0; + } +} +EXPORT_SYMBOL(v4l2_chip_match_i2c_client); + +int v4l2_chip_ident_i2c_client(struct i2c_client *c, struct v4l2_dbg_chip_ident *chip, + u32 ident, u32 revision) +{ + if (!v4l2_chip_match_i2c_client(c, &chip->match)) + return 0; + if (chip->ident == V4L2_IDENT_NONE) { + chip->ident = ident; + chip->revision = revision; + } + else { + chip->ident = V4L2_IDENT_AMBIGUOUS; + chip->revision = 0; + } + return 0; +} +EXPORT_SYMBOL(v4l2_chip_ident_i2c_client); + +/* ----------------------------------------------------------------- */ + +/* I2C Helper functions */ + + +void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, + const struct v4l2_subdev_ops *ops) +{ + v4l2_subdev_init(sd, ops); + sd->flags |= V4L2_SUBDEV_FL_IS_I2C; + /* the owner is the same as the i2c_client's driver owner */ + sd->owner = client->driver->driver.owner; + /* i2c_client and v4l2_subdev point to one another */ + v4l2_set_subdevdata(sd, client); + i2c_set_clientdata(client, sd); + /* initialize name */ + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x", + client->driver->driver.name, i2c_adapter_id(client->adapter), + client->addr); +} +EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_init); + + + +/* Load an i2c sub-device. */ +struct v4l2_subdev *v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev, + struct i2c_adapter *adapter, struct i2c_board_info *info, + const unsigned short *probe_addrs) +{ + struct v4l2_subdev *sd = NULL; + struct i2c_client *client; + + BUG_ON(!v4l2_dev); + + request_module(I2C_MODULE_PREFIX "%s", info->type); + + /* Create the i2c client */ + if (info->addr == 0 && probe_addrs) + client = i2c_new_probed_device(adapter, info, probe_addrs, + NULL); + else + client = i2c_new_device(adapter, info); + + /* Note: by loading the module first we are certain that c->driver + will be set if the driver was found. If the module was not loaded + first, then the i2c core tries to delay-load the module for us, + and then c->driver is still NULL until the module is finally + loaded. This delay-load mechanism doesn't work if other drivers + want to use the i2c device, so explicitly loading the module + is the best alternative. */ + if (client == NULL || client->driver == NULL) + goto error; + + /* Lock the module so we can safely get the v4l2_subdev pointer */ + if (!try_module_get(client->driver->driver.owner)) + goto error; + sd = i2c_get_clientdata(client); + + /* Register with the v4l2_device which increases the module's + use count as well. */ + if (v4l2_device_register_subdev(v4l2_dev, sd)) + sd = NULL; + /* Decrease the module use count to match the first try_module_get. */ + module_put(client->driver->driver.owner); + +error: + /* If we have a client but no subdev, then something went wrong and + we must unregister the client. */ + if (client && sd == NULL) + i2c_unregister_device(client); + return sd; +} +EXPORT_SYMBOL_GPL(v4l2_i2c_new_subdev_board); + +struct v4l2_subdev *v4l2_i2c_new_subdev(struct v4l2_device *v4l2_dev, + struct i2c_adapter *adapter, const char *client_type, + u8 addr, const unsigned short *probe_addrs) +{ + struct i2c_board_info info; + + /* Setup the i2c board info with the device type and + the device address. */ + memset(&info, 0, sizeof(info)); + strlcpy(info.type, client_type, sizeof(info.type)); + info.addr = addr; + + return v4l2_i2c_new_subdev_board(v4l2_dev, adapter, &info, probe_addrs); +} +EXPORT_SYMBOL_GPL(v4l2_i2c_new_subdev); + +/* Return i2c client address of v4l2_subdev. */ +unsigned short v4l2_i2c_subdev_addr(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return client ? client->addr : I2C_CLIENT_END; +} +EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_addr); + +/* Return a list of I2C tuner addresses to probe. Use only if the tuner + addresses are unknown. */ +const unsigned short *v4l2_i2c_tuner_addrs(enum v4l2_i2c_tuner_type type) +{ + static const unsigned short radio_addrs[] = { +#if defined(CONFIG_MEDIA_TUNER_TEA5761) || defined(CONFIG_MEDIA_TUNER_TEA5761_MODULE) + 0x10, +#endif + 0x60, + I2C_CLIENT_END + }; + static const unsigned short demod_addrs[] = { + 0x42, 0x43, 0x4a, 0x4b, + I2C_CLIENT_END + }; + static const unsigned short tv_addrs[] = { + 0x42, 0x43, 0x4a, 0x4b, /* tda8290 */ + 0x60, 0x61, 0x62, 0x63, 0x64, + I2C_CLIENT_END + }; + + switch (type) { + case ADDRS_RADIO: + return radio_addrs; + case ADDRS_DEMOD: + return demod_addrs; + case ADDRS_TV: + return tv_addrs; + case ADDRS_TV_WITH_DEMOD: + return tv_addrs + 4; + } + return NULL; +} +EXPORT_SYMBOL_GPL(v4l2_i2c_tuner_addrs); + +#endif /* defined(CONFIG_I2C) */ + +#if defined(CONFIG_SPI) + +/* Load an spi sub-device. */ + +void v4l2_spi_subdev_init(struct v4l2_subdev *sd, struct spi_device *spi, + const struct v4l2_subdev_ops *ops) +{ + v4l2_subdev_init(sd, ops); + sd->flags |= V4L2_SUBDEV_FL_IS_SPI; + /* the owner is the same as the spi_device's driver owner */ + sd->owner = spi->dev.driver->owner; + /* spi_device and v4l2_subdev point to one another */ + v4l2_set_subdevdata(sd, spi); + spi_set_drvdata(spi, sd); + /* initialize name */ + strlcpy(sd->name, spi->dev.driver->name, sizeof(sd->name)); +} +EXPORT_SYMBOL_GPL(v4l2_spi_subdev_init); + +struct v4l2_subdev *v4l2_spi_new_subdev(struct v4l2_device *v4l2_dev, + struct spi_master *master, struct spi_board_info *info) +{ + struct v4l2_subdev *sd = NULL; + struct spi_device *spi = NULL; + + BUG_ON(!v4l2_dev); + + if (info->modalias) + request_module(info->modalias); + + spi = spi_new_device(master, info); + + if (spi == NULL || spi->dev.driver == NULL) + goto error; + + if (!try_module_get(spi->dev.driver->owner)) + goto error; + + sd = spi_get_drvdata(spi); + + /* Register with the v4l2_device which increases the module's + use count as well. */ + if (v4l2_device_register_subdev(v4l2_dev, sd)) + sd = NULL; + + /* Decrease the module use count to match the first try_module_get. */ + module_put(spi->dev.driver->owner); + +error: + /* If we have a client but no subdev, then something went wrong and + we must unregister the client. */ + if (spi && sd == NULL) + spi_unregister_device(spi); + + return sd; +} +EXPORT_SYMBOL_GPL(v4l2_spi_new_subdev); + +#endif /* defined(CONFIG_SPI) */ + +/* Clamp x to be between min and max, aligned to a multiple of 2^align. min + * and max don't have to be aligned, but there must be at least one valid + * value. E.g., min=17,max=31,align=4 is not allowed as there are no multiples + * of 16 between 17 and 31. */ +static unsigned int clamp_align(unsigned int x, unsigned int min, + unsigned int max, unsigned int align) +{ + /* Bits that must be zero to be aligned */ + unsigned int mask = ~((1 << align) - 1); + + /* Round to nearest aligned value */ + if (align) + x = (x + (1 << (align - 1))) & mask; + + /* Clamp to aligned value of min and max */ + if (x < min) + x = (min + ~mask) & mask; + else if (x > max) + x = max & mask; + + return x; +} + +/* Bound an image to have a width between wmin and wmax, and height between + * hmin and hmax, inclusive. Additionally, the width will be a multiple of + * 2^walign, the height will be a multiple of 2^halign, and the overall size + * (width*height) will be a multiple of 2^salign. The image may be shrunk + * or enlarged to fit the alignment constraints. + * + * The width or height maximum must not be smaller than the corresponding + * minimum. The alignments must not be so high there are no possible image + * sizes within the allowed bounds. wmin and hmin must be at least 1 + * (don't use 0). If you don't care about a certain alignment, specify 0, + * as 2^0 is 1 and one byte alignment is equivalent to no alignment. If + * you only want to adjust downward, specify a maximum that's the same as + * the initial value. + */ +void v4l_bound_align_image(u32 *w, unsigned int wmin, unsigned int wmax, + unsigned int walign, + u32 *h, unsigned int hmin, unsigned int hmax, + unsigned int halign, unsigned int salign) +{ + *w = clamp_align(*w, wmin, wmax, walign); + *h = clamp_align(*h, hmin, hmax, halign); + + /* Usually we don't need to align the size and are done now. */ + if (!salign) + return; + + /* How much alignment do we have? */ + walign = __ffs(*w); + halign = __ffs(*h); + /* Enough to satisfy the image alignment? */ + if (walign + halign < salign) { + /* Max walign where there is still a valid width */ + unsigned int wmaxa = __fls(wmax ^ (wmin - 1)); + /* Max halign where there is still a valid height */ + unsigned int hmaxa = __fls(hmax ^ (hmin - 1)); + + /* up the smaller alignment until we have enough */ + do { + if (halign >= hmaxa || + (walign <= halign && walign < wmaxa)) { + *w = clamp_align(*w, wmin, wmax, walign + 1); + walign = __ffs(*w); + } else { + *h = clamp_align(*h, hmin, hmax, halign + 1); + halign = __ffs(*h); + } + } while (halign + walign < salign); + } +} +EXPORT_SYMBOL_GPL(v4l_bound_align_image); + +/** + * v4l_fill_dv_preset_info - fill description of a digital video preset + * @preset - preset value + * @info - pointer to struct v4l2_dv_enum_preset + * + * drivers can use this helper function to fill description of dv preset + * in info. + */ +int v4l_fill_dv_preset_info(u32 preset, struct v4l2_dv_enum_preset *info) +{ + static const struct v4l2_dv_preset_info { + u16 width; + u16 height; + const char *name; + } dv_presets[] = { + { 0, 0, "Invalid" }, /* V4L2_DV_INVALID */ + { 720, 480, "480p@59.94" }, /* V4L2_DV_480P59_94 */ + { 720, 576, "576p@50" }, /* V4L2_DV_576P50 */ + { 1280, 720, "720p@24" }, /* V4L2_DV_720P24 */ + { 1280, 720, "720p@25" }, /* V4L2_DV_720P25 */ + { 1280, 720, "720p@30" }, /* V4L2_DV_720P30 */ + { 1280, 720, "720p@50" }, /* V4L2_DV_720P50 */ + { 1280, 720, "720p@59.94" }, /* V4L2_DV_720P59_94 */ + { 1280, 720, "720p@60" }, /* V4L2_DV_720P60 */ + { 1920, 1080, "1080i@29.97" }, /* V4L2_DV_1080I29_97 */ + { 1920, 1080, "1080i@30" }, /* V4L2_DV_1080I30 */ + { 1920, 1080, "1080i@25" }, /* V4L2_DV_1080I25 */ + { 1920, 1080, "1080i@50" }, /* V4L2_DV_1080I50 */ + { 1920, 1080, "1080i@60" }, /* V4L2_DV_1080I60 */ + { 1920, 1080, "1080p@24" }, /* V4L2_DV_1080P24 */ + { 1920, 1080, "1080p@25" }, /* V4L2_DV_1080P25 */ + { 1920, 1080, "1080p@30" }, /* V4L2_DV_1080P30 */ + { 1920, 1080, "1080p@50" }, /* V4L2_DV_1080P50 */ + { 1920, 1080, "1080p@60" }, /* V4L2_DV_1080P60 */ + }; + + if (info == NULL || preset >= ARRAY_SIZE(dv_presets)) + return -EINVAL; + + info->preset = preset; + info->width = dv_presets[preset].width; + info->height = dv_presets[preset].height; + strlcpy(info->name, dv_presets[preset].name, sizeof(info->name)); + return 0; +} +EXPORT_SYMBOL_GPL(v4l_fill_dv_preset_info); + +const struct v4l2_frmsize_discrete *v4l2_find_nearest_format( + const struct v4l2_discrete_probe *probe, + s32 width, s32 height) +{ + int i; + u32 error, min_error = UINT_MAX; + const struct v4l2_frmsize_discrete *size, *best = NULL; + + if (!probe) + return best; + + for (i = 0, size = probe->sizes; i < probe->num_sizes; i++, size++) { + error = abs(size->width - width) + abs(size->height - height); + if (error < min_error) { + min_error = error; + best = size; + } + if (!error) + break; + } + + return best; +} +EXPORT_SYMBOL_GPL(v4l2_find_nearest_format); diff --git a/drivers/media/v4l2-core/v4l2-compat-ioctl32.c b/drivers/media/v4l2-core/v4l2-compat-ioctl32.c new file mode 100644 index 000000000000..9ebd5c540d10 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-compat-ioctl32.c @@ -0,0 +1,1045 @@ +/* + * ioctl32.c: Conversion between 32bit and 64bit native ioctls. + * Separated from fs stuff by Arnd Bergmann <arnd@arndb.de> + * + * Copyright (C) 1997-2000 Jakub Jelinek (jakub@redhat.com) + * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 2001,2002 Andi Kleen, SuSE Labs + * Copyright (C) 2003 Pavel Machek (pavel@ucw.cz) + * Copyright (C) 2005 Philippe De Muyter (phdm@macqel.be) + * Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl> + * + * These routines maintain argument size conversion between 32bit and 64bit + * ioctls. + */ + +#include <linux/compat.h> +#include <linux/module.h> +#include <linux/videodev2.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +static long native_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + long ret = -ENOIOCTLCMD; + + if (file->f_op->unlocked_ioctl) + ret = file->f_op->unlocked_ioctl(file, cmd, arg); + + return ret; +} + + +struct v4l2_clip32 { + struct v4l2_rect c; + compat_caddr_t next; +}; + +struct v4l2_window32 { + struct v4l2_rect w; + __u32 field; /* enum v4l2_field */ + __u32 chromakey; + compat_caddr_t clips; /* actually struct v4l2_clip32 * */ + __u32 clipcount; + compat_caddr_t bitmap; +}; + +static int get_v4l2_window32(struct v4l2_window *kp, struct v4l2_window32 __user *up) +{ + if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_window32)) || + copy_from_user(&kp->w, &up->w, sizeof(up->w)) || + get_user(kp->field, &up->field) || + get_user(kp->chromakey, &up->chromakey) || + get_user(kp->clipcount, &up->clipcount)) + return -EFAULT; + if (kp->clipcount > 2048) + return -EINVAL; + if (kp->clipcount) { + struct v4l2_clip32 __user *uclips; + struct v4l2_clip __user *kclips; + int n = kp->clipcount; + compat_caddr_t p; + + if (get_user(p, &up->clips)) + return -EFAULT; + uclips = compat_ptr(p); + kclips = compat_alloc_user_space(n * sizeof(struct v4l2_clip)); + kp->clips = kclips; + while (--n >= 0) { + if (copy_in_user(&kclips->c, &uclips->c, sizeof(uclips->c))) + return -EFAULT; + if (put_user(n ? kclips + 1 : NULL, &kclips->next)) + return -EFAULT; + uclips += 1; + kclips += 1; + } + } else + kp->clips = NULL; + return 0; +} + +static int put_v4l2_window32(struct v4l2_window *kp, struct v4l2_window32 __user *up) +{ + if (copy_to_user(&up->w, &kp->w, sizeof(kp->w)) || + put_user(kp->field, &up->field) || + put_user(kp->chromakey, &up->chromakey) || + put_user(kp->clipcount, &up->clipcount)) + return -EFAULT; + return 0; +} + +static inline int get_v4l2_pix_format(struct v4l2_pix_format *kp, struct v4l2_pix_format __user *up) +{ + if (copy_from_user(kp, up, sizeof(struct v4l2_pix_format))) + return -EFAULT; + return 0; +} + +static inline int get_v4l2_pix_format_mplane(struct v4l2_pix_format_mplane *kp, + struct v4l2_pix_format_mplane __user *up) +{ + if (copy_from_user(kp, up, sizeof(struct v4l2_pix_format_mplane))) + return -EFAULT; + return 0; +} + +static inline int put_v4l2_pix_format(struct v4l2_pix_format *kp, struct v4l2_pix_format __user *up) +{ + if (copy_to_user(up, kp, sizeof(struct v4l2_pix_format))) + return -EFAULT; + return 0; +} + +static inline int put_v4l2_pix_format_mplane(struct v4l2_pix_format_mplane *kp, + struct v4l2_pix_format_mplane __user *up) +{ + if (copy_to_user(up, kp, sizeof(struct v4l2_pix_format_mplane))) + return -EFAULT; + return 0; +} + +static inline int get_v4l2_vbi_format(struct v4l2_vbi_format *kp, struct v4l2_vbi_format __user *up) +{ + if (copy_from_user(kp, up, sizeof(struct v4l2_vbi_format))) + return -EFAULT; + return 0; +} + +static inline int put_v4l2_vbi_format(struct v4l2_vbi_format *kp, struct v4l2_vbi_format __user *up) +{ + if (copy_to_user(up, kp, sizeof(struct v4l2_vbi_format))) + return -EFAULT; + return 0; +} + +static inline int get_v4l2_sliced_vbi_format(struct v4l2_sliced_vbi_format *kp, struct v4l2_sliced_vbi_format __user *up) +{ + if (copy_from_user(kp, up, sizeof(struct v4l2_sliced_vbi_format))) + return -EFAULT; + return 0; +} + +static inline int put_v4l2_sliced_vbi_format(struct v4l2_sliced_vbi_format *kp, struct v4l2_sliced_vbi_format __user *up) +{ + if (copy_to_user(up, kp, sizeof(struct v4l2_sliced_vbi_format))) + return -EFAULT; + return 0; +} + +struct v4l2_format32 { + __u32 type; /* enum v4l2_buf_type */ + union { + struct v4l2_pix_format pix; + struct v4l2_pix_format_mplane pix_mp; + struct v4l2_window32 win; + struct v4l2_vbi_format vbi; + struct v4l2_sliced_vbi_format sliced; + __u8 raw_data[200]; /* user-defined */ + } fmt; +}; + +/** + * struct v4l2_create_buffers32 - VIDIOC_CREATE_BUFS32 argument + * @index: on return, index of the first created buffer + * @count: entry: number of requested buffers, + * return: number of created buffers + * @memory: buffer memory type + * @format: frame format, for which buffers are requested + * @reserved: future extensions + */ +struct v4l2_create_buffers32 { + __u32 index; + __u32 count; + __u32 memory; /* enum v4l2_memory */ + struct v4l2_format32 format; + __u32 reserved[8]; +}; + +static int __get_v4l2_format32(struct v4l2_format *kp, struct v4l2_format32 __user *up) +{ + switch (kp->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return get_v4l2_pix_format(&kp->fmt.pix, &up->fmt.pix); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return get_v4l2_pix_format_mplane(&kp->fmt.pix_mp, + &up->fmt.pix_mp); + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + return get_v4l2_window32(&kp->fmt.win, &up->fmt.win); + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + return get_v4l2_vbi_format(&kp->fmt.vbi, &up->fmt.vbi); + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + return get_v4l2_sliced_vbi_format(&kp->fmt.sliced, &up->fmt.sliced); + case V4L2_BUF_TYPE_PRIVATE: + if (copy_from_user(kp, up, sizeof(kp->fmt.raw_data))) + return -EFAULT; + return 0; + default: + printk(KERN_INFO "compat_ioctl32: unexpected VIDIOC_FMT type %d\n", + kp->type); + return -EINVAL; + } +} + +static int get_v4l2_format32(struct v4l2_format *kp, struct v4l2_format32 __user *up) +{ + if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_format32)) || + get_user(kp->type, &up->type)) + return -EFAULT; + return __get_v4l2_format32(kp, up); +} + +static int get_v4l2_create32(struct v4l2_create_buffers *kp, struct v4l2_create_buffers32 __user *up) +{ + if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_create_buffers32)) || + copy_from_user(kp, up, offsetof(struct v4l2_create_buffers32, format.fmt))) + return -EFAULT; + return __get_v4l2_format32(&kp->format, &up->format); +} + +static int __put_v4l2_format32(struct v4l2_format *kp, struct v4l2_format32 __user *up) +{ + switch (kp->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return put_v4l2_pix_format(&kp->fmt.pix, &up->fmt.pix); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return put_v4l2_pix_format_mplane(&kp->fmt.pix_mp, + &up->fmt.pix_mp); + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + return put_v4l2_window32(&kp->fmt.win, &up->fmt.win); + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + return put_v4l2_vbi_format(&kp->fmt.vbi, &up->fmt.vbi); + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + return put_v4l2_sliced_vbi_format(&kp->fmt.sliced, &up->fmt.sliced); + case V4L2_BUF_TYPE_PRIVATE: + if (copy_to_user(up, kp, sizeof(up->fmt.raw_data))) + return -EFAULT; + return 0; + default: + printk(KERN_INFO "compat_ioctl32: unexpected VIDIOC_FMT type %d\n", + kp->type); + return -EINVAL; + } +} + +static int put_v4l2_format32(struct v4l2_format *kp, struct v4l2_format32 __user *up) +{ + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_format32)) || + put_user(kp->type, &up->type)) + return -EFAULT; + return __put_v4l2_format32(kp, up); +} + +static int put_v4l2_create32(struct v4l2_create_buffers *kp, struct v4l2_create_buffers32 __user *up) +{ + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_create_buffers32)) || + copy_to_user(up, kp, offsetof(struct v4l2_create_buffers32, format.fmt))) + return -EFAULT; + return __put_v4l2_format32(&kp->format, &up->format); +} + +struct v4l2_standard32 { + __u32 index; + __u32 id[2]; /* __u64 would get the alignment wrong */ + __u8 name[24]; + struct v4l2_fract frameperiod; /* Frames, not fields */ + __u32 framelines; + __u32 reserved[4]; +}; + +static int get_v4l2_standard32(struct v4l2_standard *kp, struct v4l2_standard32 __user *up) +{ + /* other fields are not set by the user, nor used by the driver */ + if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_standard32)) || + get_user(kp->index, &up->index)) + return -EFAULT; + return 0; +} + +static int put_v4l2_standard32(struct v4l2_standard *kp, struct v4l2_standard32 __user *up) +{ + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_standard32)) || + put_user(kp->index, &up->index) || + copy_to_user(up->id, &kp->id, sizeof(__u64)) || + copy_to_user(up->name, kp->name, 24) || + copy_to_user(&up->frameperiod, &kp->frameperiod, sizeof(kp->frameperiod)) || + put_user(kp->framelines, &up->framelines) || + copy_to_user(up->reserved, kp->reserved, 4 * sizeof(__u32))) + return -EFAULT; + return 0; +} + +struct v4l2_plane32 { + __u32 bytesused; + __u32 length; + union { + __u32 mem_offset; + compat_long_t userptr; + } m; + __u32 data_offset; + __u32 reserved[11]; +}; + +struct v4l2_buffer32 { + __u32 index; + __u32 type; /* enum v4l2_buf_type */ + __u32 bytesused; + __u32 flags; + __u32 field; /* enum v4l2_field */ + struct compat_timeval timestamp; + struct v4l2_timecode timecode; + __u32 sequence; + + /* memory location */ + __u32 memory; /* enum v4l2_memory */ + union { + __u32 offset; + compat_long_t userptr; + compat_caddr_t planes; + } m; + __u32 length; + __u32 reserved2; + __u32 reserved; +}; + +static int get_v4l2_plane32(struct v4l2_plane *up, struct v4l2_plane32 *up32, + enum v4l2_memory memory) +{ + void __user *up_pln; + compat_long_t p; + + if (copy_in_user(up, up32, 2 * sizeof(__u32)) || + copy_in_user(&up->data_offset, &up32->data_offset, + sizeof(__u32))) + return -EFAULT; + + if (memory == V4L2_MEMORY_USERPTR) { + if (get_user(p, &up32->m.userptr)) + return -EFAULT; + up_pln = compat_ptr(p); + if (put_user((unsigned long)up_pln, &up->m.userptr)) + return -EFAULT; + } else { + if (copy_in_user(&up->m.mem_offset, &up32->m.mem_offset, + sizeof(__u32))) + return -EFAULT; + } + + return 0; +} + +static int put_v4l2_plane32(struct v4l2_plane *up, struct v4l2_plane32 *up32, + enum v4l2_memory memory) +{ + if (copy_in_user(up32, up, 2 * sizeof(__u32)) || + copy_in_user(&up32->data_offset, &up->data_offset, + sizeof(__u32))) + return -EFAULT; + + /* For MMAP, driver might've set up the offset, so copy it back. + * USERPTR stays the same (was userspace-provided), so no copying. */ + if (memory == V4L2_MEMORY_MMAP) + if (copy_in_user(&up32->m.mem_offset, &up->m.mem_offset, + sizeof(__u32))) + return -EFAULT; + + return 0; +} + +static int get_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user *up) +{ + struct v4l2_plane32 __user *uplane32; + struct v4l2_plane __user *uplane; + compat_caddr_t p; + int num_planes; + int ret; + + if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_buffer32)) || + get_user(kp->index, &up->index) || + get_user(kp->type, &up->type) || + get_user(kp->flags, &up->flags) || + get_user(kp->memory, &up->memory)) + return -EFAULT; + + if (V4L2_TYPE_IS_OUTPUT(kp->type)) + if (get_user(kp->bytesused, &up->bytesused) || + get_user(kp->field, &up->field) || + get_user(kp->timestamp.tv_sec, &up->timestamp.tv_sec) || + get_user(kp->timestamp.tv_usec, + &up->timestamp.tv_usec)) + return -EFAULT; + + if (V4L2_TYPE_IS_MULTIPLANAR(kp->type)) { + if (get_user(kp->length, &up->length)) + return -EFAULT; + + num_planes = kp->length; + if (num_planes == 0) { + kp->m.planes = NULL; + /* num_planes == 0 is legal, e.g. when userspace doesn't + * need planes array on DQBUF*/ + return 0; + } + + if (get_user(p, &up->m.planes)) + return -EFAULT; + + uplane32 = compat_ptr(p); + if (!access_ok(VERIFY_READ, uplane32, + num_planes * sizeof(struct v4l2_plane32))) + return -EFAULT; + + /* We don't really care if userspace decides to kill itself + * by passing a very big num_planes value */ + uplane = compat_alloc_user_space(num_planes * + sizeof(struct v4l2_plane)); + kp->m.planes = uplane; + + while (--num_planes >= 0) { + ret = get_v4l2_plane32(uplane, uplane32, kp->memory); + if (ret) + return ret; + ++uplane; + ++uplane32; + } + } else { + switch (kp->memory) { + case V4L2_MEMORY_MMAP: + if (get_user(kp->length, &up->length) || + get_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + case V4L2_MEMORY_USERPTR: + { + compat_long_t tmp; + + if (get_user(kp->length, &up->length) || + get_user(tmp, &up->m.userptr)) + return -EFAULT; + + kp->m.userptr = (unsigned long)compat_ptr(tmp); + } + break; + case V4L2_MEMORY_OVERLAY: + if (get_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + } + } + + return 0; +} + +static int put_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user *up) +{ + struct v4l2_plane32 __user *uplane32; + struct v4l2_plane __user *uplane; + compat_caddr_t p; + int num_planes; + int ret; + + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_buffer32)) || + put_user(kp->index, &up->index) || + put_user(kp->type, &up->type) || + put_user(kp->flags, &up->flags) || + put_user(kp->memory, &up->memory)) + return -EFAULT; + + if (put_user(kp->bytesused, &up->bytesused) || + put_user(kp->field, &up->field) || + put_user(kp->timestamp.tv_sec, &up->timestamp.tv_sec) || + put_user(kp->timestamp.tv_usec, &up->timestamp.tv_usec) || + copy_to_user(&up->timecode, &kp->timecode, sizeof(struct v4l2_timecode)) || + put_user(kp->sequence, &up->sequence) || + put_user(kp->reserved2, &up->reserved2) || + put_user(kp->reserved, &up->reserved)) + return -EFAULT; + + if (V4L2_TYPE_IS_MULTIPLANAR(kp->type)) { + num_planes = kp->length; + if (num_planes == 0) + return 0; + + uplane = kp->m.planes; + if (get_user(p, &up->m.planes)) + return -EFAULT; + uplane32 = compat_ptr(p); + + while (--num_planes >= 0) { + ret = put_v4l2_plane32(uplane, uplane32, kp->memory); + if (ret) + return ret; + ++uplane; + ++uplane32; + } + } else { + switch (kp->memory) { + case V4L2_MEMORY_MMAP: + if (put_user(kp->length, &up->length) || + put_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + case V4L2_MEMORY_USERPTR: + if (put_user(kp->length, &up->length) || + put_user(kp->m.userptr, &up->m.userptr)) + return -EFAULT; + break; + case V4L2_MEMORY_OVERLAY: + if (put_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + } + } + + return 0; +} + +struct v4l2_framebuffer32 { + __u32 capability; + __u32 flags; + compat_caddr_t base; + struct v4l2_pix_format fmt; +}; + +static int get_v4l2_framebuffer32(struct v4l2_framebuffer *kp, struct v4l2_framebuffer32 __user *up) +{ + u32 tmp; + + if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_framebuffer32)) || + get_user(tmp, &up->base) || + get_user(kp->capability, &up->capability) || + get_user(kp->flags, &up->flags)) + return -EFAULT; + kp->base = compat_ptr(tmp); + get_v4l2_pix_format(&kp->fmt, &up->fmt); + return 0; +} + +static int put_v4l2_framebuffer32(struct v4l2_framebuffer *kp, struct v4l2_framebuffer32 __user *up) +{ + u32 tmp = (u32)((unsigned long)kp->base); + + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_framebuffer32)) || + put_user(tmp, &up->base) || + put_user(kp->capability, &up->capability) || + put_user(kp->flags, &up->flags)) + return -EFAULT; + put_v4l2_pix_format(&kp->fmt, &up->fmt); + return 0; +} + +struct v4l2_input32 { + __u32 index; /* Which input */ + __u8 name[32]; /* Label */ + __u32 type; /* Type of input */ + __u32 audioset; /* Associated audios (bitfield) */ + __u32 tuner; /* Associated tuner */ + v4l2_std_id std; + __u32 status; + __u32 reserved[4]; +} __attribute__ ((packed)); + +/* The 64-bit v4l2_input struct has extra padding at the end of the struct. + Otherwise it is identical to the 32-bit version. */ +static inline int get_v4l2_input32(struct v4l2_input *kp, struct v4l2_input32 __user *up) +{ + if (copy_from_user(kp, up, sizeof(struct v4l2_input32))) + return -EFAULT; + return 0; +} + +static inline int put_v4l2_input32(struct v4l2_input *kp, struct v4l2_input32 __user *up) +{ + if (copy_to_user(up, kp, sizeof(struct v4l2_input32))) + return -EFAULT; + return 0; +} + +struct v4l2_ext_controls32 { + __u32 ctrl_class; + __u32 count; + __u32 error_idx; + __u32 reserved[2]; + compat_caddr_t controls; /* actually struct v4l2_ext_control32 * */ +}; + +struct v4l2_ext_control32 { + __u32 id; + __u32 size; + __u32 reserved2[1]; + union { + __s32 value; + __s64 value64; + compat_caddr_t string; /* actually char * */ + }; +} __attribute__ ((packed)); + +/* The following function really belong in v4l2-common, but that causes + a circular dependency between modules. We need to think about this, but + for now this will do. */ + +/* Return non-zero if this control is a pointer type. Currently only + type STRING is a pointer type. */ +static inline int ctrl_is_pointer(u32 id) +{ + switch (id) { + case V4L2_CID_RDS_TX_PS_NAME: + case V4L2_CID_RDS_TX_RADIO_TEXT: + return 1; + default: + return 0; + } +} + +static int get_v4l2_ext_controls32(struct v4l2_ext_controls *kp, struct v4l2_ext_controls32 __user *up) +{ + struct v4l2_ext_control32 __user *ucontrols; + struct v4l2_ext_control __user *kcontrols; + int n; + compat_caddr_t p; + + if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_ext_controls32)) || + get_user(kp->ctrl_class, &up->ctrl_class) || + get_user(kp->count, &up->count) || + get_user(kp->error_idx, &up->error_idx) || + copy_from_user(kp->reserved, up->reserved, sizeof(kp->reserved))) + return -EFAULT; + n = kp->count; + if (n == 0) { + kp->controls = NULL; + return 0; + } + if (get_user(p, &up->controls)) + return -EFAULT; + ucontrols = compat_ptr(p); + if (!access_ok(VERIFY_READ, ucontrols, + n * sizeof(struct v4l2_ext_control32))) + return -EFAULT; + kcontrols = compat_alloc_user_space(n * sizeof(struct v4l2_ext_control)); + kp->controls = kcontrols; + while (--n >= 0) { + if (copy_in_user(kcontrols, ucontrols, sizeof(*ucontrols))) + return -EFAULT; + if (ctrl_is_pointer(kcontrols->id)) { + void __user *s; + + if (get_user(p, &ucontrols->string)) + return -EFAULT; + s = compat_ptr(p); + if (put_user(s, &kcontrols->string)) + return -EFAULT; + } + ucontrols++; + kcontrols++; + } + return 0; +} + +static int put_v4l2_ext_controls32(struct v4l2_ext_controls *kp, struct v4l2_ext_controls32 __user *up) +{ + struct v4l2_ext_control32 __user *ucontrols; + struct v4l2_ext_control __user *kcontrols = kp->controls; + int n = kp->count; + compat_caddr_t p; + + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_ext_controls32)) || + put_user(kp->ctrl_class, &up->ctrl_class) || + put_user(kp->count, &up->count) || + put_user(kp->error_idx, &up->error_idx) || + copy_to_user(up->reserved, kp->reserved, sizeof(up->reserved))) + return -EFAULT; + if (!kp->count) + return 0; + + if (get_user(p, &up->controls)) + return -EFAULT; + ucontrols = compat_ptr(p); + if (!access_ok(VERIFY_WRITE, ucontrols, + n * sizeof(struct v4l2_ext_control32))) + return -EFAULT; + + while (--n >= 0) { + unsigned size = sizeof(*ucontrols); + + /* Do not modify the pointer when copying a pointer control. + The contents of the pointer was changed, not the pointer + itself. */ + if (ctrl_is_pointer(kcontrols->id)) + size -= sizeof(ucontrols->value64); + if (copy_in_user(ucontrols, kcontrols, size)) + return -EFAULT; + ucontrols++; + kcontrols++; + } + return 0; +} + +struct v4l2_event32 { + __u32 type; + union { + __u8 data[64]; + } u; + __u32 pending; + __u32 sequence; + struct compat_timespec timestamp; + __u32 id; + __u32 reserved[8]; +}; + +static int put_v4l2_event32(struct v4l2_event *kp, struct v4l2_event32 __user *up) +{ + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_event32)) || + put_user(kp->type, &up->type) || + copy_to_user(&up->u, &kp->u, sizeof(kp->u)) || + put_user(kp->pending, &up->pending) || + put_user(kp->sequence, &up->sequence) || + put_compat_timespec(&kp->timestamp, &up->timestamp) || + put_user(kp->id, &up->id) || + copy_to_user(up->reserved, kp->reserved, 8 * sizeof(__u32))) + return -EFAULT; + return 0; +} + +#define VIDIOC_G_FMT32 _IOWR('V', 4, struct v4l2_format32) +#define VIDIOC_S_FMT32 _IOWR('V', 5, struct v4l2_format32) +#define VIDIOC_QUERYBUF32 _IOWR('V', 9, struct v4l2_buffer32) +#define VIDIOC_G_FBUF32 _IOR ('V', 10, struct v4l2_framebuffer32) +#define VIDIOC_S_FBUF32 _IOW ('V', 11, struct v4l2_framebuffer32) +#define VIDIOC_QBUF32 _IOWR('V', 15, struct v4l2_buffer32) +#define VIDIOC_DQBUF32 _IOWR('V', 17, struct v4l2_buffer32) +#define VIDIOC_ENUMSTD32 _IOWR('V', 25, struct v4l2_standard32) +#define VIDIOC_ENUMINPUT32 _IOWR('V', 26, struct v4l2_input32) +#define VIDIOC_TRY_FMT32 _IOWR('V', 64, struct v4l2_format32) +#define VIDIOC_G_EXT_CTRLS32 _IOWR('V', 71, struct v4l2_ext_controls32) +#define VIDIOC_S_EXT_CTRLS32 _IOWR('V', 72, struct v4l2_ext_controls32) +#define VIDIOC_TRY_EXT_CTRLS32 _IOWR('V', 73, struct v4l2_ext_controls32) +#define VIDIOC_DQEVENT32 _IOR ('V', 89, struct v4l2_event32) +#define VIDIOC_CREATE_BUFS32 _IOWR('V', 92, struct v4l2_create_buffers32) +#define VIDIOC_PREPARE_BUF32 _IOWR('V', 93, struct v4l2_buffer32) + +#define VIDIOC_OVERLAY32 _IOW ('V', 14, s32) +#define VIDIOC_STREAMON32 _IOW ('V', 18, s32) +#define VIDIOC_STREAMOFF32 _IOW ('V', 19, s32) +#define VIDIOC_G_INPUT32 _IOR ('V', 38, s32) +#define VIDIOC_S_INPUT32 _IOWR('V', 39, s32) +#define VIDIOC_G_OUTPUT32 _IOR ('V', 46, s32) +#define VIDIOC_S_OUTPUT32 _IOWR('V', 47, s32) + +static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + union { + struct v4l2_format v2f; + struct v4l2_buffer v2b; + struct v4l2_framebuffer v2fb; + struct v4l2_input v2i; + struct v4l2_standard v2s; + struct v4l2_ext_controls v2ecs; + struct v4l2_event v2ev; + struct v4l2_create_buffers v2crt; + unsigned long vx; + int vi; + } karg; + void __user *up = compat_ptr(arg); + int compatible_arg = 1; + long err = 0; + + /* First, convert the command. */ + switch (cmd) { + case VIDIOC_G_FMT32: cmd = VIDIOC_G_FMT; break; + case VIDIOC_S_FMT32: cmd = VIDIOC_S_FMT; break; + case VIDIOC_QUERYBUF32: cmd = VIDIOC_QUERYBUF; break; + case VIDIOC_G_FBUF32: cmd = VIDIOC_G_FBUF; break; + case VIDIOC_S_FBUF32: cmd = VIDIOC_S_FBUF; break; + case VIDIOC_QBUF32: cmd = VIDIOC_QBUF; break; + case VIDIOC_DQBUF32: cmd = VIDIOC_DQBUF; break; + case VIDIOC_ENUMSTD32: cmd = VIDIOC_ENUMSTD; break; + case VIDIOC_ENUMINPUT32: cmd = VIDIOC_ENUMINPUT; break; + case VIDIOC_TRY_FMT32: cmd = VIDIOC_TRY_FMT; break; + case VIDIOC_G_EXT_CTRLS32: cmd = VIDIOC_G_EXT_CTRLS; break; + case VIDIOC_S_EXT_CTRLS32: cmd = VIDIOC_S_EXT_CTRLS; break; + case VIDIOC_TRY_EXT_CTRLS32: cmd = VIDIOC_TRY_EXT_CTRLS; break; + case VIDIOC_DQEVENT32: cmd = VIDIOC_DQEVENT; break; + case VIDIOC_OVERLAY32: cmd = VIDIOC_OVERLAY; break; + case VIDIOC_STREAMON32: cmd = VIDIOC_STREAMON; break; + case VIDIOC_STREAMOFF32: cmd = VIDIOC_STREAMOFF; break; + case VIDIOC_G_INPUT32: cmd = VIDIOC_G_INPUT; break; + case VIDIOC_S_INPUT32: cmd = VIDIOC_S_INPUT; break; + case VIDIOC_G_OUTPUT32: cmd = VIDIOC_G_OUTPUT; break; + case VIDIOC_S_OUTPUT32: cmd = VIDIOC_S_OUTPUT; break; + case VIDIOC_CREATE_BUFS32: cmd = VIDIOC_CREATE_BUFS; break; + case VIDIOC_PREPARE_BUF32: cmd = VIDIOC_PREPARE_BUF; break; + } + + switch (cmd) { + case VIDIOC_OVERLAY: + case VIDIOC_STREAMON: + case VIDIOC_STREAMOFF: + case VIDIOC_S_INPUT: + case VIDIOC_S_OUTPUT: + err = get_user(karg.vi, (s32 __user *)up); + compatible_arg = 0; + break; + + case VIDIOC_G_INPUT: + case VIDIOC_G_OUTPUT: + compatible_arg = 0; + break; + + case VIDIOC_G_FMT: + case VIDIOC_S_FMT: + case VIDIOC_TRY_FMT: + err = get_v4l2_format32(&karg.v2f, up); + compatible_arg = 0; + break; + + case VIDIOC_CREATE_BUFS: + err = get_v4l2_create32(&karg.v2crt, up); + compatible_arg = 0; + break; + + case VIDIOC_PREPARE_BUF: + case VIDIOC_QUERYBUF: + case VIDIOC_QBUF: + case VIDIOC_DQBUF: + err = get_v4l2_buffer32(&karg.v2b, up); + compatible_arg = 0; + break; + + case VIDIOC_S_FBUF: + err = get_v4l2_framebuffer32(&karg.v2fb, up); + compatible_arg = 0; + break; + + case VIDIOC_G_FBUF: + compatible_arg = 0; + break; + + case VIDIOC_ENUMSTD: + err = get_v4l2_standard32(&karg.v2s, up); + compatible_arg = 0; + break; + + case VIDIOC_ENUMINPUT: + err = get_v4l2_input32(&karg.v2i, up); + compatible_arg = 0; + break; + + case VIDIOC_G_EXT_CTRLS: + case VIDIOC_S_EXT_CTRLS: + case VIDIOC_TRY_EXT_CTRLS: + err = get_v4l2_ext_controls32(&karg.v2ecs, up); + compatible_arg = 0; + break; + case VIDIOC_DQEVENT: + compatible_arg = 0; + break; + } + if (err) + return err; + + if (compatible_arg) + err = native_ioctl(file, cmd, (unsigned long)up); + else { + mm_segment_t old_fs = get_fs(); + + set_fs(KERNEL_DS); + err = native_ioctl(file, cmd, (unsigned long)&karg); + set_fs(old_fs); + } + + /* Special case: even after an error we need to put the + results back for these ioctls since the error_idx will + contain information on which control failed. */ + switch (cmd) { + case VIDIOC_G_EXT_CTRLS: + case VIDIOC_S_EXT_CTRLS: + case VIDIOC_TRY_EXT_CTRLS: + if (put_v4l2_ext_controls32(&karg.v2ecs, up)) + err = -EFAULT; + break; + } + if (err) + return err; + + switch (cmd) { + case VIDIOC_S_INPUT: + case VIDIOC_S_OUTPUT: + case VIDIOC_G_INPUT: + case VIDIOC_G_OUTPUT: + err = put_user(((s32)karg.vi), (s32 __user *)up); + break; + + case VIDIOC_G_FBUF: + err = put_v4l2_framebuffer32(&karg.v2fb, up); + break; + + case VIDIOC_DQEVENT: + err = put_v4l2_event32(&karg.v2ev, up); + break; + + case VIDIOC_G_FMT: + case VIDIOC_S_FMT: + case VIDIOC_TRY_FMT: + err = put_v4l2_format32(&karg.v2f, up); + break; + + case VIDIOC_CREATE_BUFS: + err = put_v4l2_create32(&karg.v2crt, up); + break; + + case VIDIOC_QUERYBUF: + case VIDIOC_QBUF: + case VIDIOC_DQBUF: + err = put_v4l2_buffer32(&karg.v2b, up); + break; + + case VIDIOC_ENUMSTD: + err = put_v4l2_standard32(&karg.v2s, up); + break; + + case VIDIOC_ENUMINPUT: + err = put_v4l2_input32(&karg.v2i, up); + break; + } + return err; +} + +long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct video_device *vdev = video_devdata(file); + long ret = -ENOIOCTLCMD; + + if (!file->f_op->unlocked_ioctl) + return ret; + + switch (cmd) { + case VIDIOC_QUERYCAP: + case VIDIOC_RESERVED: + case VIDIOC_ENUM_FMT: + case VIDIOC_G_FMT32: + case VIDIOC_S_FMT32: + case VIDIOC_REQBUFS: + case VIDIOC_QUERYBUF32: + case VIDIOC_G_FBUF32: + case VIDIOC_S_FBUF32: + case VIDIOC_OVERLAY32: + case VIDIOC_QBUF32: + case VIDIOC_DQBUF32: + case VIDIOC_STREAMON32: + case VIDIOC_STREAMOFF32: + case VIDIOC_G_PARM: + case VIDIOC_S_PARM: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_ENUMSTD32: + case VIDIOC_ENUMINPUT32: + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_AUDIO: + case VIDIOC_S_AUDIO: + case VIDIOC_QUERYCTRL: + case VIDIOC_QUERYMENU: + case VIDIOC_G_INPUT32: + case VIDIOC_S_INPUT32: + case VIDIOC_G_OUTPUT32: + case VIDIOC_S_OUTPUT32: + case VIDIOC_ENUMOUTPUT: + case VIDIOC_G_AUDOUT: + case VIDIOC_S_AUDOUT: + case VIDIOC_G_MODULATOR: + case VIDIOC_S_MODULATOR: + case VIDIOC_S_FREQUENCY: + case VIDIOC_G_FREQUENCY: + case VIDIOC_CROPCAP: + case VIDIOC_G_CROP: + case VIDIOC_S_CROP: + case VIDIOC_G_SELECTION: + case VIDIOC_S_SELECTION: + case VIDIOC_G_JPEGCOMP: + case VIDIOC_S_JPEGCOMP: + case VIDIOC_QUERYSTD: + case VIDIOC_TRY_FMT32: + case VIDIOC_ENUMAUDIO: + case VIDIOC_ENUMAUDOUT: + case VIDIOC_G_PRIORITY: + case VIDIOC_S_PRIORITY: + case VIDIOC_G_SLICED_VBI_CAP: + case VIDIOC_LOG_STATUS: + case VIDIOC_G_EXT_CTRLS32: + case VIDIOC_S_EXT_CTRLS32: + case VIDIOC_TRY_EXT_CTRLS32: + case VIDIOC_ENUM_FRAMESIZES: + case VIDIOC_ENUM_FRAMEINTERVALS: + case VIDIOC_G_ENC_INDEX: + case VIDIOC_ENCODER_CMD: + case VIDIOC_TRY_ENCODER_CMD: + case VIDIOC_DECODER_CMD: + case VIDIOC_TRY_DECODER_CMD: + case VIDIOC_DBG_S_REGISTER: + case VIDIOC_DBG_G_REGISTER: + case VIDIOC_DBG_G_CHIP_IDENT: + case VIDIOC_S_HW_FREQ_SEEK: + case VIDIOC_ENUM_DV_PRESETS: + case VIDIOC_S_DV_PRESET: + case VIDIOC_G_DV_PRESET: + case VIDIOC_QUERY_DV_PRESET: + case VIDIOC_S_DV_TIMINGS: + case VIDIOC_G_DV_TIMINGS: + case VIDIOC_DQEVENT: + case VIDIOC_DQEVENT32: + case VIDIOC_SUBSCRIBE_EVENT: + case VIDIOC_UNSUBSCRIBE_EVENT: + case VIDIOC_CREATE_BUFS32: + case VIDIOC_PREPARE_BUF32: + case VIDIOC_ENUM_DV_TIMINGS: + case VIDIOC_QUERY_DV_TIMINGS: + case VIDIOC_DV_TIMINGS_CAP: + case VIDIOC_ENUM_FREQ_BANDS: + ret = do_video_ioctl(file, cmd, arg); + break; + + default: + if (vdev->fops->compat_ioctl32) + ret = vdev->fops->compat_ioctl32(file, cmd, arg); + + if (ret == -ENOIOCTLCMD) + printk(KERN_WARNING "compat_ioctl32: " + "unknown ioctl '%c', dir=%d, #%d (0x%08x)\n", + _IOC_TYPE(cmd), _IOC_DIR(cmd), _IOC_NR(cmd), + cmd); + break; + } + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_compat_ioctl32); diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c new file mode 100644 index 000000000000..b6a2ee71e5c3 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-ctrls.c @@ -0,0 +1,2651 @@ +/* + V4L2 controls framework implementation. + + Copyright (C) 2010 Hans Verkuil <hverkuil@xs4all.nl> + + 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 + */ + +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-dev.h> + +#define has_op(master, op) \ + (master->ops && master->ops->op) +#define call_op(master, op) \ + (has_op(master, op) ? master->ops->op(master) : 0) + +/* Internal temporary helper struct, one for each v4l2_ext_control */ +struct v4l2_ctrl_helper { + /* Pointer to the control reference of the master control */ + struct v4l2_ctrl_ref *mref; + /* The control corresponding to the v4l2_ext_control ID field. */ + struct v4l2_ctrl *ctrl; + /* v4l2_ext_control index of the next control belonging to the + same cluster, or 0 if there isn't any. */ + u32 next; +}; + +/* Small helper function to determine if the autocluster is set to manual + mode. */ +static bool is_cur_manual(const struct v4l2_ctrl *master) +{ + return master->is_auto && master->cur.val == master->manual_mode_value; +} + +/* Same as above, but this checks the against the new value instead of the + current value. */ +static bool is_new_manual(const struct v4l2_ctrl *master) +{ + return master->is_auto && master->val == master->manual_mode_value; +} + +/* Returns NULL or a character pointer array containing the menu for + the given control ID. The pointer array ends with a NULL pointer. + An empty string signifies a menu entry that is invalid. This allows + drivers to disable certain options if it is not supported. */ +const char * const *v4l2_ctrl_get_menu(u32 id) +{ + static const char * const mpeg_audio_sampling_freq[] = { + "44.1 kHz", + "48 kHz", + "32 kHz", + NULL + }; + static const char * const mpeg_audio_encoding[] = { + "MPEG-1/2 Layer I", + "MPEG-1/2 Layer II", + "MPEG-1/2 Layer III", + "MPEG-2/4 AAC", + "AC-3", + NULL + }; + static const char * const mpeg_audio_l1_bitrate[] = { + "32 kbps", + "64 kbps", + "96 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "288 kbps", + "320 kbps", + "352 kbps", + "384 kbps", + "416 kbps", + "448 kbps", + NULL + }; + static const char * const mpeg_audio_l2_bitrate[] = { + "32 kbps", + "48 kbps", + "56 kbps", + "64 kbps", + "80 kbps", + "96 kbps", + "112 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "320 kbps", + "384 kbps", + NULL + }; + static const char * const mpeg_audio_l3_bitrate[] = { + "32 kbps", + "40 kbps", + "48 kbps", + "56 kbps", + "64 kbps", + "80 kbps", + "96 kbps", + "112 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "320 kbps", + NULL + }; + static const char * const mpeg_audio_ac3_bitrate[] = { + "32 kbps", + "40 kbps", + "48 kbps", + "56 kbps", + "64 kbps", + "80 kbps", + "96 kbps", + "112 kbps", + "128 kbps", + "160 kbps", + "192 kbps", + "224 kbps", + "256 kbps", + "320 kbps", + "384 kbps", + "448 kbps", + "512 kbps", + "576 kbps", + "640 kbps", + NULL + }; + static const char * const mpeg_audio_mode[] = { + "Stereo", + "Joint Stereo", + "Dual", + "Mono", + NULL + }; + static const char * const mpeg_audio_mode_extension[] = { + "Bound 4", + "Bound 8", + "Bound 12", + "Bound 16", + NULL + }; + static const char * const mpeg_audio_emphasis[] = { + "No Emphasis", + "50/15 us", + "CCITT J17", + NULL + }; + static const char * const mpeg_audio_crc[] = { + "No CRC", + "16-bit CRC", + NULL + }; + static const char * const mpeg_audio_dec_playback[] = { + "Auto", + "Stereo", + "Left", + "Right", + "Mono", + "Swapped Stereo", + NULL + }; + static const char * const mpeg_video_encoding[] = { + "MPEG-1", + "MPEG-2", + "MPEG-4 AVC", + NULL + }; + static const char * const mpeg_video_aspect[] = { + "1x1", + "4x3", + "16x9", + "2.21x1", + NULL + }; + static const char * const mpeg_video_bitrate_mode[] = { + "Variable Bitrate", + "Constant Bitrate", + NULL + }; + static const char * const mpeg_stream_type[] = { + "MPEG-2 Program Stream", + "MPEG-2 Transport Stream", + "MPEG-1 System Stream", + "MPEG-2 DVD-compatible Stream", + "MPEG-1 VCD-compatible Stream", + "MPEG-2 SVCD-compatible Stream", + NULL + }; + static const char * const mpeg_stream_vbi_fmt[] = { + "No VBI", + "Private Packet, IVTV Format", + NULL + }; + static const char * const camera_power_line_frequency[] = { + "Disabled", + "50 Hz", + "60 Hz", + "Auto", + NULL + }; + static const char * const camera_exposure_auto[] = { + "Auto Mode", + "Manual Mode", + "Shutter Priority Mode", + "Aperture Priority Mode", + NULL + }; + static const char * const camera_exposure_metering[] = { + "Average", + "Center Weighted", + "Spot", + NULL + }; + static const char * const camera_auto_focus_range[] = { + "Auto", + "Normal", + "Macro", + "Infinity", + NULL + }; + static const char * const colorfx[] = { + "None", + "Black & White", + "Sepia", + "Negative", + "Emboss", + "Sketch", + "Sky Blue", + "Grass Green", + "Skin Whiten", + "Vivid", + "Aqua", + "Art Freeze", + "Silhouette", + "Solarization", + "Antique", + "Set Cb/Cr", + NULL + }; + static const char * const auto_n_preset_white_balance[] = { + "Manual", + "Auto", + "Incandescent", + "Fluorescent", + "Fluorescent H", + "Horizon", + "Daylight", + "Flash", + "Cloudy", + "Shade", + NULL, + }; + static const char * const camera_iso_sensitivity_auto[] = { + "Manual", + "Auto", + NULL + }; + static const char * const scene_mode[] = { + "None", + "Backlight", + "Beach/Snow", + "Candle Light", + "Dusk/Dawn", + "Fall Colors", + "Fireworks", + "Landscape", + "Night", + "Party/Indoor", + "Portrait", + "Sports", + "Sunset", + "Text", + NULL + }; + static const char * const tune_preemphasis[] = { + "No Preemphasis", + "50 Microseconds", + "75 Microseconds", + NULL, + }; + static const char * const header_mode[] = { + "Separate Buffer", + "Joined With 1st Frame", + NULL, + }; + static const char * const multi_slice[] = { + "Single", + "Max Macroblocks", + "Max Bytes", + NULL, + }; + static const char * const entropy_mode[] = { + "CAVLC", + "CABAC", + NULL, + }; + static const char * const mpeg_h264_level[] = { + "1", + "1b", + "1.1", + "1.2", + "1.3", + "2", + "2.1", + "2.2", + "3", + "3.1", + "3.2", + "4", + "4.1", + "4.2", + "5", + "5.1", + NULL, + }; + static const char * const h264_loop_filter[] = { + "Enabled", + "Disabled", + "Disabled at Slice Boundary", + NULL, + }; + static const char * const h264_profile[] = { + "Baseline", + "Constrained Baseline", + "Main", + "Extended", + "High", + "High 10", + "High 422", + "High 444 Predictive", + "High 10 Intra", + "High 422 Intra", + "High 444 Intra", + "CAVLC 444 Intra", + "Scalable Baseline", + "Scalable High", + "Scalable High Intra", + "Multiview High", + NULL, + }; + static const char * const vui_sar_idc[] = { + "Unspecified", + "1:1", + "12:11", + "10:11", + "16:11", + "40:33", + "24:11", + "20:11", + "32:11", + "80:33", + "18:11", + "15:11", + "64:33", + "160:99", + "4:3", + "3:2", + "2:1", + "Extended SAR", + NULL, + }; + static const char * const mpeg_mpeg4_level[] = { + "0", + "0b", + "1", + "2", + "3", + "3b", + "4", + "5", + NULL, + }; + static const char * const mpeg4_profile[] = { + "Simple", + "Advanced Simple", + "Core", + "Simple Scalable", + "Advanced Coding Efficency", + NULL, + }; + + static const char * const flash_led_mode[] = { + "Off", + "Flash", + "Torch", + NULL, + }; + static const char * const flash_strobe_source[] = { + "Software", + "External", + NULL, + }; + + static const char * const jpeg_chroma_subsampling[] = { + "4:4:4", + "4:2:2", + "4:2:0", + "4:1:1", + "4:1:0", + "Gray", + NULL, + }; + + switch (id) { + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: + return mpeg_audio_sampling_freq; + case V4L2_CID_MPEG_AUDIO_ENCODING: + return mpeg_audio_encoding; + case V4L2_CID_MPEG_AUDIO_L1_BITRATE: + return mpeg_audio_l1_bitrate; + case V4L2_CID_MPEG_AUDIO_L2_BITRATE: + return mpeg_audio_l2_bitrate; + case V4L2_CID_MPEG_AUDIO_L3_BITRATE: + return mpeg_audio_l3_bitrate; + case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: + return mpeg_audio_ac3_bitrate; + case V4L2_CID_MPEG_AUDIO_MODE: + return mpeg_audio_mode; + case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: + return mpeg_audio_mode_extension; + case V4L2_CID_MPEG_AUDIO_EMPHASIS: + return mpeg_audio_emphasis; + case V4L2_CID_MPEG_AUDIO_CRC: + return mpeg_audio_crc; + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: + case V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK: + return mpeg_audio_dec_playback; + case V4L2_CID_MPEG_VIDEO_ENCODING: + return mpeg_video_encoding; + case V4L2_CID_MPEG_VIDEO_ASPECT: + return mpeg_video_aspect; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + return mpeg_video_bitrate_mode; + case V4L2_CID_MPEG_STREAM_TYPE: + return mpeg_stream_type; + case V4L2_CID_MPEG_STREAM_VBI_FMT: + return mpeg_stream_vbi_fmt; + case V4L2_CID_POWER_LINE_FREQUENCY: + return camera_power_line_frequency; + case V4L2_CID_EXPOSURE_AUTO: + return camera_exposure_auto; + case V4L2_CID_EXPOSURE_METERING: + return camera_exposure_metering; + case V4L2_CID_AUTO_FOCUS_RANGE: + return camera_auto_focus_range; + case V4L2_CID_COLORFX: + return colorfx; + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: + return auto_n_preset_white_balance; + case V4L2_CID_ISO_SENSITIVITY_AUTO: + return camera_iso_sensitivity_auto; + case V4L2_CID_SCENE_MODE: + return scene_mode; + case V4L2_CID_TUNE_PREEMPHASIS: + return tune_preemphasis; + case V4L2_CID_FLASH_LED_MODE: + return flash_led_mode; + case V4L2_CID_FLASH_STROBE_SOURCE: + return flash_strobe_source; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + return header_mode; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + return multi_slice; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + return entropy_mode; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + return mpeg_h264_level; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + return h264_loop_filter; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + return h264_profile; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC: + return vui_sar_idc; + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + return mpeg_mpeg4_level; + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + return mpeg4_profile; + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + return jpeg_chroma_subsampling; + + default: + return NULL; + } +} +EXPORT_SYMBOL(v4l2_ctrl_get_menu); + +/* Return the control name. */ +const char *v4l2_ctrl_get_name(u32 id) +{ + switch (id) { + /* USER controls */ + /* Keep the order of the 'case's the same as in videodev2.h! */ + case V4L2_CID_USER_CLASS: return "User Controls"; + case V4L2_CID_BRIGHTNESS: return "Brightness"; + case V4L2_CID_CONTRAST: return "Contrast"; + case V4L2_CID_SATURATION: return "Saturation"; + case V4L2_CID_HUE: return "Hue"; + case V4L2_CID_AUDIO_VOLUME: return "Volume"; + case V4L2_CID_AUDIO_BALANCE: return "Balance"; + case V4L2_CID_AUDIO_BASS: return "Bass"; + case V4L2_CID_AUDIO_TREBLE: return "Treble"; + case V4L2_CID_AUDIO_MUTE: return "Mute"; + case V4L2_CID_AUDIO_LOUDNESS: return "Loudness"; + case V4L2_CID_BLACK_LEVEL: return "Black Level"; + case V4L2_CID_AUTO_WHITE_BALANCE: return "White Balance, Automatic"; + case V4L2_CID_DO_WHITE_BALANCE: return "Do White Balance"; + case V4L2_CID_RED_BALANCE: return "Red Balance"; + case V4L2_CID_BLUE_BALANCE: return "Blue Balance"; + case V4L2_CID_GAMMA: return "Gamma"; + case V4L2_CID_EXPOSURE: return "Exposure"; + case V4L2_CID_AUTOGAIN: return "Gain, Automatic"; + case V4L2_CID_GAIN: return "Gain"; + case V4L2_CID_HFLIP: return "Horizontal Flip"; + case V4L2_CID_VFLIP: return "Vertical Flip"; + case V4L2_CID_HCENTER: return "Horizontal Center"; + case V4L2_CID_VCENTER: return "Vertical Center"; + case V4L2_CID_POWER_LINE_FREQUENCY: return "Power Line Frequency"; + case V4L2_CID_HUE_AUTO: return "Hue, Automatic"; + case V4L2_CID_WHITE_BALANCE_TEMPERATURE: return "White Balance Temperature"; + case V4L2_CID_SHARPNESS: return "Sharpness"; + case V4L2_CID_BACKLIGHT_COMPENSATION: return "Backlight Compensation"; + case V4L2_CID_CHROMA_AGC: return "Chroma AGC"; + case V4L2_CID_COLOR_KILLER: return "Color Killer"; + case V4L2_CID_COLORFX: return "Color Effects"; + case V4L2_CID_AUTOBRIGHTNESS: return "Brightness, Automatic"; + case V4L2_CID_BAND_STOP_FILTER: return "Band-Stop Filter"; + case V4L2_CID_ROTATE: return "Rotate"; + case V4L2_CID_BG_COLOR: return "Background Color"; + case V4L2_CID_CHROMA_GAIN: return "Chroma Gain"; + case V4L2_CID_ILLUMINATORS_1: return "Illuminator 1"; + case V4L2_CID_ILLUMINATORS_2: return "Illuminator 2"; + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: return "Min Number of Capture Buffers"; + case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: return "Min Number of Output Buffers"; + case V4L2_CID_ALPHA_COMPONENT: return "Alpha Component"; + case V4L2_CID_COLORFX_CBCR: return "Color Effects, CbCr"; + + /* MPEG controls */ + /* Keep the order of the 'case's the same as in videodev2.h! */ + case V4L2_CID_MPEG_CLASS: return "MPEG Encoder Controls"; + case V4L2_CID_MPEG_STREAM_TYPE: return "Stream Type"; + case V4L2_CID_MPEG_STREAM_PID_PMT: return "Stream PMT Program ID"; + case V4L2_CID_MPEG_STREAM_PID_AUDIO: return "Stream Audio Program ID"; + case V4L2_CID_MPEG_STREAM_PID_VIDEO: return "Stream Video Program ID"; + case V4L2_CID_MPEG_STREAM_PID_PCR: return "Stream PCR Program ID"; + case V4L2_CID_MPEG_STREAM_PES_ID_AUDIO: return "Stream PES Audio ID"; + case V4L2_CID_MPEG_STREAM_PES_ID_VIDEO: return "Stream PES Video ID"; + case V4L2_CID_MPEG_STREAM_VBI_FMT: return "Stream VBI Format"; + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: return "Audio Sampling Frequency"; + case V4L2_CID_MPEG_AUDIO_ENCODING: return "Audio Encoding"; + case V4L2_CID_MPEG_AUDIO_L1_BITRATE: return "Audio Layer I Bitrate"; + case V4L2_CID_MPEG_AUDIO_L2_BITRATE: return "Audio Layer II Bitrate"; + case V4L2_CID_MPEG_AUDIO_L3_BITRATE: return "Audio Layer III Bitrate"; + case V4L2_CID_MPEG_AUDIO_MODE: return "Audio Stereo Mode"; + case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: return "Audio Stereo Mode Extension"; + case V4L2_CID_MPEG_AUDIO_EMPHASIS: return "Audio Emphasis"; + case V4L2_CID_MPEG_AUDIO_CRC: return "Audio CRC"; + case V4L2_CID_MPEG_AUDIO_MUTE: return "Audio Mute"; + case V4L2_CID_MPEG_AUDIO_AAC_BITRATE: return "Audio AAC Bitrate"; + case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: return "Audio AC-3 Bitrate"; + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: return "Audio Playback"; + case V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK: return "Audio Multilingual Playback"; + case V4L2_CID_MPEG_VIDEO_ENCODING: return "Video Encoding"; + case V4L2_CID_MPEG_VIDEO_ASPECT: return "Video Aspect"; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: return "Video B Frames"; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: return "Video GOP Size"; + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: return "Video GOP Closure"; + case V4L2_CID_MPEG_VIDEO_PULLDOWN: return "Video Pulldown"; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: return "Video Bitrate Mode"; + case V4L2_CID_MPEG_VIDEO_BITRATE: return "Video Bitrate"; + case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: return "Video Peak Bitrate"; + case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: return "Video Temporal Decimation"; + case V4L2_CID_MPEG_VIDEO_MUTE: return "Video Mute"; + case V4L2_CID_MPEG_VIDEO_MUTE_YUV: return "Video Mute YUV"; + case V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE: return "Decoder Slice Interface"; + case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: return "MPEG4 Loop Filter Enable"; + case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: return "Number of Intra Refresh MBs"; + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: return "Frame Level Rate Control Enable"; + case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE: return "H264 MB Level Rate Control"; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: return "Sequence Header Mode"; + case V4L2_CID_MPEG_VIDEO_MAX_REF_PIC: return "Max Number of Reference Pics"; + case V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP: return "H263 I-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP: return "H263 P-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP: return "H263 B-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_H263_MIN_QP: return "H263 Minimum QP Value"; + case V4L2_CID_MPEG_VIDEO_H263_MAX_QP: return "H263 Maximum QP Value"; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: return "H264 I-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: return "H264 P-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: return "H264 B-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: return "H264 Maximum QP Value"; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: return "H264 Minimum QP Value"; + case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM: return "H264 8x8 Transform Enable"; + case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE: return "H264 CPB Buffer Size"; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: return "H264 Entropy Mode"; + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: return "H264 I-Frame Period"; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: return "H264 Level"; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA: return "H264 Loop Filter Alpha Offset"; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA: return "H264 Loop Filter Beta Offset"; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: return "H264 Loop Filter Mode"; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: return "H264 Profile"; + case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT: return "Vertical Size of SAR"; + case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH: return "Horizontal Size of SAR"; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE: return "Aspect Ratio VUI Enable"; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC: return "VUI Aspect Ratio IDC"; + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: return "MPEG4 I-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: return "MPEG4 P-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP: return "MPEG4 B-Frame QP Value"; + case V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP: return "MPEG4 Minimum QP Value"; + case V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP: return "MPEG4 Maximum QP Value"; + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: return "MPEG4 Level"; + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: return "MPEG4 Profile"; + case V4L2_CID_MPEG_VIDEO_MPEG4_QPEL: return "Quarter Pixel Search Enable"; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: return "Maximum Bytes in a Slice"; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: return "Number of MBs in a Slice"; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: return "Slice Partitioning Method"; + case V4L2_CID_MPEG_VIDEO_VBV_SIZE: return "VBV Buffer Size"; + case V4L2_CID_MPEG_VIDEO_DEC_PTS: return "Video Decoder PTS"; + case V4L2_CID_MPEG_VIDEO_DEC_FRAME: return "Video Decoder Frame Count"; + + /* CAMERA controls */ + /* Keep the order of the 'case's the same as in videodev2.h! */ + case V4L2_CID_CAMERA_CLASS: return "Camera Controls"; + case V4L2_CID_EXPOSURE_AUTO: return "Auto Exposure"; + case V4L2_CID_EXPOSURE_ABSOLUTE: return "Exposure Time, Absolute"; + case V4L2_CID_EXPOSURE_AUTO_PRIORITY: return "Exposure, Dynamic Framerate"; + case V4L2_CID_PAN_RELATIVE: return "Pan, Relative"; + case V4L2_CID_TILT_RELATIVE: return "Tilt, Relative"; + case V4L2_CID_PAN_RESET: return "Pan, Reset"; + case V4L2_CID_TILT_RESET: return "Tilt, Reset"; + case V4L2_CID_PAN_ABSOLUTE: return "Pan, Absolute"; + case V4L2_CID_TILT_ABSOLUTE: return "Tilt, Absolute"; + case V4L2_CID_FOCUS_ABSOLUTE: return "Focus, Absolute"; + case V4L2_CID_FOCUS_RELATIVE: return "Focus, Relative"; + case V4L2_CID_FOCUS_AUTO: return "Focus, Automatic Continuous"; + case V4L2_CID_ZOOM_ABSOLUTE: return "Zoom, Absolute"; + case V4L2_CID_ZOOM_RELATIVE: return "Zoom, Relative"; + case V4L2_CID_ZOOM_CONTINUOUS: return "Zoom, Continuous"; + case V4L2_CID_PRIVACY: return "Privacy"; + case V4L2_CID_IRIS_ABSOLUTE: return "Iris, Absolute"; + case V4L2_CID_IRIS_RELATIVE: return "Iris, Relative"; + case V4L2_CID_AUTO_EXPOSURE_BIAS: return "Auto Exposure, Bias"; + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: return "White Balance, Auto & Preset"; + case V4L2_CID_WIDE_DYNAMIC_RANGE: return "Wide Dynamic Range"; + case V4L2_CID_IMAGE_STABILIZATION: return "Image Stabilization"; + case V4L2_CID_ISO_SENSITIVITY: return "ISO Sensitivity"; + case V4L2_CID_ISO_SENSITIVITY_AUTO: return "ISO Sensitivity, Auto"; + case V4L2_CID_EXPOSURE_METERING: return "Exposure, Metering Mode"; + case V4L2_CID_SCENE_MODE: return "Scene Mode"; + case V4L2_CID_3A_LOCK: return "3A Lock"; + case V4L2_CID_AUTO_FOCUS_START: return "Auto Focus, Start"; + case V4L2_CID_AUTO_FOCUS_STOP: return "Auto Focus, Stop"; + case V4L2_CID_AUTO_FOCUS_STATUS: return "Auto Focus, Status"; + case V4L2_CID_AUTO_FOCUS_RANGE: return "Auto Focus, Range"; + + /* FM Radio Modulator control */ + /* Keep the order of the 'case's the same as in videodev2.h! */ + case V4L2_CID_FM_TX_CLASS: return "FM Radio Modulator Controls"; + case V4L2_CID_RDS_TX_DEVIATION: return "RDS Signal Deviation"; + case V4L2_CID_RDS_TX_PI: return "RDS Program ID"; + case V4L2_CID_RDS_TX_PTY: return "RDS Program Type"; + case V4L2_CID_RDS_TX_PS_NAME: return "RDS PS Name"; + case V4L2_CID_RDS_TX_RADIO_TEXT: return "RDS Radio Text"; + case V4L2_CID_AUDIO_LIMITER_ENABLED: return "Audio Limiter Feature Enabled"; + case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: return "Audio Limiter Release Time"; + case V4L2_CID_AUDIO_LIMITER_DEVIATION: return "Audio Limiter Deviation"; + case V4L2_CID_AUDIO_COMPRESSION_ENABLED: return "Audio Compression Enabled"; + case V4L2_CID_AUDIO_COMPRESSION_GAIN: return "Audio Compression Gain"; + case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: return "Audio Compression Threshold"; + case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: return "Audio Compression Attack Time"; + case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: return "Audio Compression Release Time"; + case V4L2_CID_PILOT_TONE_ENABLED: return "Pilot Tone Feature Enabled"; + case V4L2_CID_PILOT_TONE_DEVIATION: return "Pilot Tone Deviation"; + case V4L2_CID_PILOT_TONE_FREQUENCY: return "Pilot Tone Frequency"; + case V4L2_CID_TUNE_PREEMPHASIS: return "Pre-Emphasis"; + case V4L2_CID_TUNE_POWER_LEVEL: return "Tune Power Level"; + case V4L2_CID_TUNE_ANTENNA_CAPACITOR: return "Tune Antenna Capacitor"; + + /* Flash controls */ + case V4L2_CID_FLASH_CLASS: return "Flash Controls"; + case V4L2_CID_FLASH_LED_MODE: return "LED Mode"; + case V4L2_CID_FLASH_STROBE_SOURCE: return "Strobe Source"; + case V4L2_CID_FLASH_STROBE: return "Strobe"; + case V4L2_CID_FLASH_STROBE_STOP: return "Stop Strobe"; + case V4L2_CID_FLASH_STROBE_STATUS: return "Strobe Status"; + case V4L2_CID_FLASH_TIMEOUT: return "Strobe Timeout"; + case V4L2_CID_FLASH_INTENSITY: return "Intensity, Flash Mode"; + case V4L2_CID_FLASH_TORCH_INTENSITY: return "Intensity, Torch Mode"; + case V4L2_CID_FLASH_INDICATOR_INTENSITY: return "Intensity, Indicator"; + case V4L2_CID_FLASH_FAULT: return "Faults"; + case V4L2_CID_FLASH_CHARGE: return "Charge"; + case V4L2_CID_FLASH_READY: return "Ready to Strobe"; + + /* JPEG encoder controls */ + /* Keep the order of the 'case's the same as in videodev2.h! */ + case V4L2_CID_JPEG_CLASS: return "JPEG Compression Controls"; + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: return "Chroma Subsampling"; + case V4L2_CID_JPEG_RESTART_INTERVAL: return "Restart Interval"; + case V4L2_CID_JPEG_COMPRESSION_QUALITY: return "Compression Quality"; + case V4L2_CID_JPEG_ACTIVE_MARKER: return "Active Markers"; + + /* Image source controls */ + case V4L2_CID_IMAGE_SOURCE_CLASS: return "Image Source Controls"; + case V4L2_CID_VBLANK: return "Vertical Blanking"; + case V4L2_CID_HBLANK: return "Horizontal Blanking"; + case V4L2_CID_ANALOGUE_GAIN: return "Analogue Gain"; + + /* Image processing controls */ + case V4L2_CID_IMAGE_PROC_CLASS: return "Image Processing Controls"; + case V4L2_CID_LINK_FREQ: return "Link Frequency"; + case V4L2_CID_PIXEL_RATE: return "Pixel Rate"; + + default: + return NULL; + } +} +EXPORT_SYMBOL(v4l2_ctrl_get_name); + +void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, + s32 *min, s32 *max, s32 *step, s32 *def, u32 *flags) +{ + *name = v4l2_ctrl_get_name(id); + *flags = 0; + + switch (id) { + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_LOUDNESS: + case V4L2_CID_AUTO_WHITE_BALANCE: + case V4L2_CID_AUTOGAIN: + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + case V4L2_CID_HUE_AUTO: + case V4L2_CID_CHROMA_AGC: + case V4L2_CID_COLOR_KILLER: + case V4L2_CID_AUTOBRIGHTNESS: + case V4L2_CID_MPEG_AUDIO_MUTE: + case V4L2_CID_MPEG_VIDEO_MUTE: + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + case V4L2_CID_MPEG_VIDEO_PULLDOWN: + case V4L2_CID_EXPOSURE_AUTO_PRIORITY: + case V4L2_CID_FOCUS_AUTO: + case V4L2_CID_PRIVACY: + case V4L2_CID_AUDIO_LIMITER_ENABLED: + case V4L2_CID_AUDIO_COMPRESSION_ENABLED: + case V4L2_CID_PILOT_TONE_ENABLED: + case V4L2_CID_ILLUMINATORS_1: + case V4L2_CID_ILLUMINATORS_2: + case V4L2_CID_FLASH_STROBE_STATUS: + case V4L2_CID_FLASH_CHARGE: + case V4L2_CID_FLASH_READY: + case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: + case V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE: + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE: + case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM: + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE: + case V4L2_CID_MPEG_VIDEO_MPEG4_QPEL: + case V4L2_CID_WIDE_DYNAMIC_RANGE: + case V4L2_CID_IMAGE_STABILIZATION: + *type = V4L2_CTRL_TYPE_BOOLEAN; + *min = 0; + *max = *step = 1; + break; + case V4L2_CID_PAN_RESET: + case V4L2_CID_TILT_RESET: + case V4L2_CID_FLASH_STROBE: + case V4L2_CID_FLASH_STROBE_STOP: + case V4L2_CID_AUTO_FOCUS_START: + case V4L2_CID_AUTO_FOCUS_STOP: + *type = V4L2_CTRL_TYPE_BUTTON; + *flags |= V4L2_CTRL_FLAG_WRITE_ONLY; + *min = *max = *step = *def = 0; + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: + case V4L2_CID_MPEG_AUDIO_ENCODING: + case V4L2_CID_MPEG_AUDIO_L1_BITRATE: + case V4L2_CID_MPEG_AUDIO_L2_BITRATE: + case V4L2_CID_MPEG_AUDIO_L3_BITRATE: + case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: + case V4L2_CID_MPEG_AUDIO_MODE: + case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: + case V4L2_CID_MPEG_AUDIO_EMPHASIS: + case V4L2_CID_MPEG_AUDIO_CRC: + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: + case V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK: + case V4L2_CID_MPEG_VIDEO_ENCODING: + case V4L2_CID_MPEG_VIDEO_ASPECT: + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + case V4L2_CID_MPEG_STREAM_TYPE: + case V4L2_CID_MPEG_STREAM_VBI_FMT: + case V4L2_CID_EXPOSURE_AUTO: + case V4L2_CID_AUTO_FOCUS_RANGE: + case V4L2_CID_COLORFX: + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: + case V4L2_CID_TUNE_PREEMPHASIS: + case V4L2_CID_FLASH_LED_MODE: + case V4L2_CID_FLASH_STROBE_SOURCE: + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC: + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + case V4L2_CID_ISO_SENSITIVITY_AUTO: + case V4L2_CID_EXPOSURE_METERING: + case V4L2_CID_SCENE_MODE: + *type = V4L2_CTRL_TYPE_MENU; + break; + case V4L2_CID_LINK_FREQ: + *type = V4L2_CTRL_TYPE_INTEGER_MENU; + break; + case V4L2_CID_RDS_TX_PS_NAME: + case V4L2_CID_RDS_TX_RADIO_TEXT: + *type = V4L2_CTRL_TYPE_STRING; + break; + case V4L2_CID_ISO_SENSITIVITY: + case V4L2_CID_AUTO_EXPOSURE_BIAS: + *type = V4L2_CTRL_TYPE_INTEGER_MENU; + break; + case V4L2_CID_USER_CLASS: + case V4L2_CID_CAMERA_CLASS: + case V4L2_CID_MPEG_CLASS: + case V4L2_CID_FM_TX_CLASS: + case V4L2_CID_FLASH_CLASS: + case V4L2_CID_JPEG_CLASS: + case V4L2_CID_IMAGE_SOURCE_CLASS: + case V4L2_CID_IMAGE_PROC_CLASS: + *type = V4L2_CTRL_TYPE_CTRL_CLASS; + /* You can neither read not write these */ + *flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY; + *min = *max = *step = *def = 0; + break; + case V4L2_CID_BG_COLOR: + *type = V4L2_CTRL_TYPE_INTEGER; + *step = 1; + *min = 0; + /* Max is calculated as RGB888 that is 2^24 */ + *max = 0xFFFFFF; + break; + case V4L2_CID_FLASH_FAULT: + case V4L2_CID_JPEG_ACTIVE_MARKER: + case V4L2_CID_3A_LOCK: + case V4L2_CID_AUTO_FOCUS_STATUS: + *type = V4L2_CTRL_TYPE_BITMASK; + break; + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: + *type = V4L2_CTRL_TYPE_INTEGER; + *flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + case V4L2_CID_MPEG_VIDEO_DEC_FRAME: + case V4L2_CID_MPEG_VIDEO_DEC_PTS: + *flags |= V4L2_CTRL_FLAG_VOLATILE; + /* Fall through */ + case V4L2_CID_PIXEL_RATE: + *type = V4L2_CTRL_TYPE_INTEGER64; + *flags |= V4L2_CTRL_FLAG_READ_ONLY; + *min = *max = *step = *def = 0; + break; + default: + *type = V4L2_CTRL_TYPE_INTEGER; + break; + } + switch (id) { + case V4L2_CID_MPEG_AUDIO_ENCODING: + case V4L2_CID_MPEG_AUDIO_MODE: + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + case V4L2_CID_MPEG_STREAM_TYPE: + *flags |= V4L2_CTRL_FLAG_UPDATE; + break; + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + case V4L2_CID_RED_BALANCE: + case V4L2_CID_BLUE_BALANCE: + case V4L2_CID_GAMMA: + case V4L2_CID_SHARPNESS: + case V4L2_CID_CHROMA_GAIN: + case V4L2_CID_RDS_TX_DEVIATION: + case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: + case V4L2_CID_AUDIO_LIMITER_DEVIATION: + case V4L2_CID_AUDIO_COMPRESSION_GAIN: + case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: + case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: + case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: + case V4L2_CID_PILOT_TONE_DEVIATION: + case V4L2_CID_PILOT_TONE_FREQUENCY: + case V4L2_CID_TUNE_POWER_LEVEL: + case V4L2_CID_TUNE_ANTENNA_CAPACITOR: + *flags |= V4L2_CTRL_FLAG_SLIDER; + break; + case V4L2_CID_PAN_RELATIVE: + case V4L2_CID_TILT_RELATIVE: + case V4L2_CID_FOCUS_RELATIVE: + case V4L2_CID_IRIS_RELATIVE: + case V4L2_CID_ZOOM_RELATIVE: + *flags |= V4L2_CTRL_FLAG_WRITE_ONLY; + break; + case V4L2_CID_FLASH_STROBE_STATUS: + case V4L2_CID_AUTO_FOCUS_STATUS: + case V4L2_CID_FLASH_READY: + *flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + } +} +EXPORT_SYMBOL(v4l2_ctrl_fill); + +/* Helper function to determine whether the control type is compatible with + VIDIOC_G/S_CTRL. */ +static bool type_is_int(const struct v4l2_ctrl *ctrl) +{ + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER64: + case V4L2_CTRL_TYPE_STRING: + /* Nope, these need v4l2_ext_control */ + return false; + default: + return true; + } +} + +static void fill_event(struct v4l2_event *ev, struct v4l2_ctrl *ctrl, u32 changes) +{ + memset(ev->reserved, 0, sizeof(ev->reserved)); + ev->type = V4L2_EVENT_CTRL; + ev->id = ctrl->id; + ev->u.ctrl.changes = changes; + ev->u.ctrl.type = ctrl->type; + ev->u.ctrl.flags = ctrl->flags; + if (ctrl->type == V4L2_CTRL_TYPE_STRING) + ev->u.ctrl.value64 = 0; + else + ev->u.ctrl.value64 = ctrl->cur.val64; + ev->u.ctrl.minimum = ctrl->minimum; + ev->u.ctrl.maximum = ctrl->maximum; + if (ctrl->type == V4L2_CTRL_TYPE_MENU + || ctrl->type == V4L2_CTRL_TYPE_INTEGER_MENU) + ev->u.ctrl.step = 1; + else + ev->u.ctrl.step = ctrl->step; + ev->u.ctrl.default_value = ctrl->default_value; +} + +static void send_event(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 changes) +{ + struct v4l2_event ev; + struct v4l2_subscribed_event *sev; + + if (list_empty(&ctrl->ev_subs)) + return; + fill_event(&ev, ctrl, changes); + + list_for_each_entry(sev, &ctrl->ev_subs, node) + if (sev->fh != fh || + (sev->flags & V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK)) + v4l2_event_queue_fh(sev->fh, &ev); +} + +/* Helper function: copy the current control value back to the caller */ +static int cur_to_user(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl) +{ + u32 len; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_STRING: + len = strlen(ctrl->cur.string); + if (c->size < len + 1) { + c->size = len + 1; + return -ENOSPC; + } + return copy_to_user(c->string, ctrl->cur.string, + len + 1) ? -EFAULT : 0; + case V4L2_CTRL_TYPE_INTEGER64: + c->value64 = ctrl->cur.val64; + break; + default: + c->value = ctrl->cur.val; + break; + } + return 0; +} + +/* Helper function: copy the caller-provider value as the new control value */ +static int user_to_new(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl) +{ + int ret; + u32 size; + + ctrl->is_new = 1; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER64: + ctrl->val64 = c->value64; + break; + case V4L2_CTRL_TYPE_STRING: + size = c->size; + if (size == 0) + return -ERANGE; + if (size > ctrl->maximum + 1) + size = ctrl->maximum + 1; + ret = copy_from_user(ctrl->string, c->string, size); + if (!ret) { + char last = ctrl->string[size - 1]; + + ctrl->string[size - 1] = 0; + /* If the string was longer than ctrl->maximum, + then return an error. */ + if (strlen(ctrl->string) == ctrl->maximum && last) + return -ERANGE; + } + return ret ? -EFAULT : 0; + default: + ctrl->val = c->value; + break; + } + return 0; +} + +/* Helper function: copy the new control value back to the caller */ +static int new_to_user(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl) +{ + u32 len; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_STRING: + len = strlen(ctrl->string); + if (c->size < len + 1) { + c->size = ctrl->maximum + 1; + return -ENOSPC; + } + return copy_to_user(c->string, ctrl->string, + len + 1) ? -EFAULT : 0; + case V4L2_CTRL_TYPE_INTEGER64: + c->value64 = ctrl->val64; + break; + default: + c->value = ctrl->val; + break; + } + return 0; +} + +/* Copy the new value to the current value. */ +static void new_to_cur(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, + bool update_inactive) +{ + bool changed = false; + + if (ctrl == NULL) + return; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BUTTON: + changed = true; + break; + case V4L2_CTRL_TYPE_STRING: + /* strings are always 0-terminated */ + changed = strcmp(ctrl->string, ctrl->cur.string); + strcpy(ctrl->cur.string, ctrl->string); + break; + case V4L2_CTRL_TYPE_INTEGER64: + changed = ctrl->val64 != ctrl->cur.val64; + ctrl->cur.val64 = ctrl->val64; + break; + default: + changed = ctrl->val != ctrl->cur.val; + ctrl->cur.val = ctrl->val; + break; + } + if (update_inactive) { + /* Note: update_inactive can only be true for auto clusters. */ + ctrl->flags &= + ~(V4L2_CTRL_FLAG_INACTIVE | V4L2_CTRL_FLAG_VOLATILE); + if (!is_cur_manual(ctrl->cluster[0])) { + ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; + if (ctrl->cluster[0]->has_volatiles) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + } + fh = NULL; + } + if (changed || update_inactive) { + /* If a control was changed that was not one of the controls + modified by the application, then send the event to all. */ + if (!ctrl->is_new) + fh = NULL; + send_event(fh, ctrl, + (changed ? V4L2_EVENT_CTRL_CH_VALUE : 0) | + (update_inactive ? V4L2_EVENT_CTRL_CH_FLAGS : 0)); + } +} + +/* Copy the current value to the new value */ +static void cur_to_new(struct v4l2_ctrl *ctrl) +{ + if (ctrl == NULL) + return; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_STRING: + /* strings are always 0-terminated */ + strcpy(ctrl->string, ctrl->cur.string); + break; + case V4L2_CTRL_TYPE_INTEGER64: + ctrl->val64 = ctrl->cur.val64; + break; + default: + ctrl->val = ctrl->cur.val; + break; + } +} + +/* Return non-zero if one or more of the controls in the cluster has a new + value that differs from the current value. */ +static int cluster_changed(struct v4l2_ctrl *master) +{ + int diff = 0; + int i; + + for (i = 0; !diff && i < master->ncontrols; i++) { + struct v4l2_ctrl *ctrl = master->cluster[i]; + + if (ctrl == NULL) + continue; + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BUTTON: + /* Button controls are always 'different' */ + return 1; + case V4L2_CTRL_TYPE_STRING: + /* strings are always 0-terminated */ + diff = strcmp(ctrl->string, ctrl->cur.string); + break; + case V4L2_CTRL_TYPE_INTEGER64: + diff = ctrl->val64 != ctrl->cur.val64; + break; + default: + diff = ctrl->val != ctrl->cur.val; + break; + } + } + return diff; +} + +/* Validate integer-type control */ +static int validate_new_int(const struct v4l2_ctrl *ctrl, s32 *pval) +{ + s32 val = *pval; + u32 offset; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER: + /* Round towards the closest legal value */ + val += ctrl->step / 2; + if (val < ctrl->minimum) + val = ctrl->minimum; + if (val > ctrl->maximum) + val = ctrl->maximum; + offset = val - ctrl->minimum; + offset = ctrl->step * (offset / ctrl->step); + val = ctrl->minimum + offset; + *pval = val; + return 0; + + case V4L2_CTRL_TYPE_BOOLEAN: + *pval = !!val; + return 0; + + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER_MENU: + if (val < ctrl->minimum || val > ctrl->maximum) + return -ERANGE; + if (ctrl->menu_skip_mask & (1 << val)) + return -EINVAL; + if (ctrl->type == V4L2_CTRL_TYPE_MENU && + ctrl->qmenu[val][0] == '\0') + return -EINVAL; + return 0; + + case V4L2_CTRL_TYPE_BITMASK: + *pval &= ctrl->maximum; + return 0; + + case V4L2_CTRL_TYPE_BUTTON: + case V4L2_CTRL_TYPE_CTRL_CLASS: + *pval = 0; + return 0; + + default: + return -EINVAL; + } +} + +/* Validate a new control */ +static int validate_new(const struct v4l2_ctrl *ctrl, struct v4l2_ext_control *c) +{ + char *s = c->string; + size_t len; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER: + case V4L2_CTRL_TYPE_BOOLEAN: + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER_MENU: + case V4L2_CTRL_TYPE_BITMASK: + case V4L2_CTRL_TYPE_BUTTON: + case V4L2_CTRL_TYPE_CTRL_CLASS: + return validate_new_int(ctrl, &c->value); + + case V4L2_CTRL_TYPE_INTEGER64: + return 0; + + case V4L2_CTRL_TYPE_STRING: + len = strlen(s); + if (len < ctrl->minimum) + return -ERANGE; + if ((len - ctrl->minimum) % ctrl->step) + return -ERANGE; + return 0; + + default: + return -EINVAL; + } +} + +static inline u32 node2id(struct list_head *node) +{ + return list_entry(node, struct v4l2_ctrl_ref, node)->ctrl->id; +} + +/* Set the handler's error code if it wasn't set earlier already */ +static inline int handler_set_err(struct v4l2_ctrl_handler *hdl, int err) +{ + if (hdl->error == 0) + hdl->error = err; + return err; +} + +/* Initialize the handler */ +int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, + unsigned nr_of_controls_hint) +{ + hdl->lock = &hdl->_lock; + mutex_init(hdl->lock); + INIT_LIST_HEAD(&hdl->ctrls); + INIT_LIST_HEAD(&hdl->ctrl_refs); + hdl->nr_of_buckets = 1 + nr_of_controls_hint / 8; + hdl->buckets = kcalloc(hdl->nr_of_buckets, sizeof(hdl->buckets[0]), + GFP_KERNEL); + hdl->error = hdl->buckets ? 0 : -ENOMEM; + return hdl->error; +} +EXPORT_SYMBOL(v4l2_ctrl_handler_init); + +/* Free all controls and control refs */ +void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) +{ + struct v4l2_ctrl_ref *ref, *next_ref; + struct v4l2_ctrl *ctrl, *next_ctrl; + struct v4l2_subscribed_event *sev, *next_sev; + + if (hdl == NULL || hdl->buckets == NULL) + return; + + mutex_lock(hdl->lock); + /* Free all nodes */ + list_for_each_entry_safe(ref, next_ref, &hdl->ctrl_refs, node) { + list_del(&ref->node); + kfree(ref); + } + /* Free all controls owned by the handler */ + list_for_each_entry_safe(ctrl, next_ctrl, &hdl->ctrls, node) { + list_del(&ctrl->node); + list_for_each_entry_safe(sev, next_sev, &ctrl->ev_subs, node) + list_del(&sev->node); + kfree(ctrl); + } + kfree(hdl->buckets); + hdl->buckets = NULL; + hdl->cached = NULL; + hdl->error = 0; + mutex_unlock(hdl->lock); +} +EXPORT_SYMBOL(v4l2_ctrl_handler_free); + +/* For backwards compatibility: V4L2_CID_PRIVATE_BASE should no longer + be used except in G_CTRL, S_CTRL, QUERYCTRL and QUERYMENU when dealing + with applications that do not use the NEXT_CTRL flag. + + We just find the n-th private user control. It's O(N), but that should not + be an issue in this particular case. */ +static struct v4l2_ctrl_ref *find_private_ref( + struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref; + + id -= V4L2_CID_PRIVATE_BASE; + list_for_each_entry(ref, &hdl->ctrl_refs, node) { + /* Search for private user controls that are compatible with + VIDIOC_G/S_CTRL. */ + if (V4L2_CTRL_ID2CLASS(ref->ctrl->id) == V4L2_CTRL_CLASS_USER && + V4L2_CTRL_DRIVER_PRIV(ref->ctrl->id)) { + if (!type_is_int(ref->ctrl)) + continue; + if (id == 0) + return ref; + id--; + } + } + return NULL; +} + +/* Find a control with the given ID. */ +static struct v4l2_ctrl_ref *find_ref(struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref; + int bucket; + + id &= V4L2_CTRL_ID_MASK; + + /* Old-style private controls need special handling */ + if (id >= V4L2_CID_PRIVATE_BASE) + return find_private_ref(hdl, id); + bucket = id % hdl->nr_of_buckets; + + /* Simple optimization: cache the last control found */ + if (hdl->cached && hdl->cached->ctrl->id == id) + return hdl->cached; + + /* Not in cache, search the hash */ + ref = hdl->buckets ? hdl->buckets[bucket] : NULL; + while (ref && ref->ctrl->id != id) + ref = ref->next; + + if (ref) + hdl->cached = ref; /* cache it! */ + return ref; +} + +/* Find a control with the given ID. Take the handler's lock first. */ +static struct v4l2_ctrl_ref *find_ref_lock( + struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref = NULL; + + if (hdl) { + mutex_lock(hdl->lock); + ref = find_ref(hdl, id); + mutex_unlock(hdl->lock); + } + return ref; +} + +/* Find a control with the given ID. */ +struct v4l2_ctrl *v4l2_ctrl_find(struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl_ref *ref = find_ref_lock(hdl, id); + + return ref ? ref->ctrl : NULL; +} +EXPORT_SYMBOL(v4l2_ctrl_find); + +/* Allocate a new v4l2_ctrl_ref and hook it into the handler. */ +static int handler_new_ref(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl *ctrl) +{ + struct v4l2_ctrl_ref *ref; + struct v4l2_ctrl_ref *new_ref; + u32 id = ctrl->id; + u32 class_ctrl = V4L2_CTRL_ID2CLASS(id) | 1; + int bucket = id % hdl->nr_of_buckets; /* which bucket to use */ + + /* Automatically add the control class if it is not yet present. */ + if (id != class_ctrl && find_ref_lock(hdl, class_ctrl) == NULL) + if (!v4l2_ctrl_new_std(hdl, NULL, class_ctrl, 0, 0, 0, 0)) + return hdl->error; + + if (hdl->error) + return hdl->error; + + new_ref = kzalloc(sizeof(*new_ref), GFP_KERNEL); + if (!new_ref) + return handler_set_err(hdl, -ENOMEM); + new_ref->ctrl = ctrl; + if (ctrl->handler == hdl) { + /* By default each control starts in a cluster of its own. + new_ref->ctrl is basically a cluster array with one + element, so that's perfect to use as the cluster pointer. + But only do this for the handler that owns the control. */ + ctrl->cluster = &new_ref->ctrl; + ctrl->ncontrols = 1; + } + + INIT_LIST_HEAD(&new_ref->node); + + mutex_lock(hdl->lock); + + /* Add immediately at the end of the list if the list is empty, or if + the last element in the list has a lower ID. + This ensures that when elements are added in ascending order the + insertion is an O(1) operation. */ + if (list_empty(&hdl->ctrl_refs) || id > node2id(hdl->ctrl_refs.prev)) { + list_add_tail(&new_ref->node, &hdl->ctrl_refs); + goto insert_in_hash; + } + + /* Find insert position in sorted list */ + list_for_each_entry(ref, &hdl->ctrl_refs, node) { + if (ref->ctrl->id < id) + continue; + /* Don't add duplicates */ + if (ref->ctrl->id == id) { + kfree(new_ref); + goto unlock; + } + list_add(&new_ref->node, ref->node.prev); + break; + } + +insert_in_hash: + /* Insert the control node in the hash */ + new_ref->next = hdl->buckets[bucket]; + hdl->buckets[bucket] = new_ref; + +unlock: + mutex_unlock(hdl->lock); + return 0; +} + +/* Add a new control */ +static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, const char *name, enum v4l2_ctrl_type type, + s32 min, s32 max, u32 step, s32 def, + u32 flags, const char * const *qmenu, + const s64 *qmenu_int, void *priv) +{ + struct v4l2_ctrl *ctrl; + unsigned sz_extra = 0; + + if (hdl->error) + return NULL; + + /* Sanity checks */ + if (id == 0 || name == NULL || id >= V4L2_CID_PRIVATE_BASE || + (type == V4L2_CTRL_TYPE_INTEGER && step == 0) || + (type == V4L2_CTRL_TYPE_BITMASK && max == 0) || + (type == V4L2_CTRL_TYPE_MENU && qmenu == NULL) || + (type == V4L2_CTRL_TYPE_INTEGER_MENU && qmenu_int == NULL) || + (type == V4L2_CTRL_TYPE_STRING && max == 0)) { + handler_set_err(hdl, -ERANGE); + return NULL; + } + if (type != V4L2_CTRL_TYPE_BITMASK && max < min) { + handler_set_err(hdl, -ERANGE); + return NULL; + } + if ((type == V4L2_CTRL_TYPE_INTEGER || + type == V4L2_CTRL_TYPE_MENU || + type == V4L2_CTRL_TYPE_INTEGER_MENU || + type == V4L2_CTRL_TYPE_BOOLEAN) && + (def < min || def > max)) { + handler_set_err(hdl, -ERANGE); + return NULL; + } + if (type == V4L2_CTRL_TYPE_BITMASK && ((def & ~max) || min || step)) { + handler_set_err(hdl, -ERANGE); + return NULL; + } + + if (type == V4L2_CTRL_TYPE_BUTTON) + flags |= V4L2_CTRL_FLAG_WRITE_ONLY; + else if (type == V4L2_CTRL_TYPE_CTRL_CLASS) + flags |= V4L2_CTRL_FLAG_READ_ONLY; + else if (type == V4L2_CTRL_TYPE_STRING) + sz_extra += 2 * (max + 1); + + ctrl = kzalloc(sizeof(*ctrl) + sz_extra, GFP_KERNEL); + if (ctrl == NULL) { + handler_set_err(hdl, -ENOMEM); + return NULL; + } + + INIT_LIST_HEAD(&ctrl->node); + INIT_LIST_HEAD(&ctrl->ev_subs); + ctrl->handler = hdl; + ctrl->ops = ops; + ctrl->id = id; + ctrl->name = name; + ctrl->type = type; + ctrl->flags = flags; + ctrl->minimum = min; + ctrl->maximum = max; + ctrl->step = step; + if (type == V4L2_CTRL_TYPE_MENU) + ctrl->qmenu = qmenu; + else if (type == V4L2_CTRL_TYPE_INTEGER_MENU) + ctrl->qmenu_int = qmenu_int; + ctrl->priv = priv; + ctrl->cur.val = ctrl->val = ctrl->default_value = def; + + if (ctrl->type == V4L2_CTRL_TYPE_STRING) { + ctrl->cur.string = (char *)&ctrl[1] + sz_extra - (max + 1); + ctrl->string = (char *)&ctrl[1] + sz_extra - 2 * (max + 1); + if (ctrl->minimum) + memset(ctrl->cur.string, ' ', ctrl->minimum); + } + if (handler_new_ref(hdl, ctrl)) { + kfree(ctrl); + return NULL; + } + mutex_lock(hdl->lock); + list_add_tail(&ctrl->node, &hdl->ctrls); + mutex_unlock(hdl->lock); + return ctrl; +} + +struct v4l2_ctrl *v4l2_ctrl_new_custom(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_config *cfg, void *priv) +{ + bool is_menu; + struct v4l2_ctrl *ctrl; + const char *name = cfg->name; + const char * const *qmenu = cfg->qmenu; + const s64 *qmenu_int = cfg->qmenu_int; + enum v4l2_ctrl_type type = cfg->type; + u32 flags = cfg->flags; + s32 min = cfg->min; + s32 max = cfg->max; + u32 step = cfg->step; + s32 def = cfg->def; + + if (name == NULL) + v4l2_ctrl_fill(cfg->id, &name, &type, &min, &max, &step, + &def, &flags); + + is_menu = (cfg->type == V4L2_CTRL_TYPE_MENU || + cfg->type == V4L2_CTRL_TYPE_INTEGER_MENU); + if (is_menu) + WARN_ON(step); + else + WARN_ON(cfg->menu_skip_mask); + if (cfg->type == V4L2_CTRL_TYPE_MENU && qmenu == NULL) + qmenu = v4l2_ctrl_get_menu(cfg->id); + else if (cfg->type == V4L2_CTRL_TYPE_INTEGER_MENU && + qmenu_int == NULL) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + + ctrl = v4l2_ctrl_new(hdl, cfg->ops, cfg->id, name, + type, min, max, + is_menu ? cfg->menu_skip_mask : step, + def, flags, qmenu, qmenu_int, priv); + if (ctrl) + ctrl->is_private = cfg->is_private; + return ctrl; +} +EXPORT_SYMBOL(v4l2_ctrl_new_custom); + +/* Helper function for standard non-menu controls */ +struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s32 min, s32 max, u32 step, s32 def) +{ + const char *name; + enum v4l2_ctrl_type type; + u32 flags; + + v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags); + if (type == V4L2_CTRL_TYPE_MENU + || type == V4L2_CTRL_TYPE_INTEGER_MENU) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + return v4l2_ctrl_new(hdl, ops, id, name, type, + min, max, step, def, flags, NULL, NULL, NULL); +} +EXPORT_SYMBOL(v4l2_ctrl_new_std); + +/* Helper function for standard menu controls */ +struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s32 max, s32 mask, s32 def) +{ + const char * const *qmenu = v4l2_ctrl_get_menu(id); + const char *name; + enum v4l2_ctrl_type type; + s32 min; + s32 step; + u32 flags; + + v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags); + if (type != V4L2_CTRL_TYPE_MENU) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + return v4l2_ctrl_new(hdl, ops, id, name, type, + 0, max, mask, def, flags, qmenu, NULL, NULL); +} +EXPORT_SYMBOL(v4l2_ctrl_new_std_menu); + +/* Helper function for standard integer menu controls */ +struct v4l2_ctrl *v4l2_ctrl_new_int_menu(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s32 max, s32 def, const s64 *qmenu_int) +{ + const char *name; + enum v4l2_ctrl_type type; + s32 min; + s32 step; + u32 flags; + + v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags); + if (type != V4L2_CTRL_TYPE_INTEGER_MENU) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + return v4l2_ctrl_new(hdl, ops, id, name, type, + 0, max, 0, def, flags, NULL, qmenu_int, NULL); +} +EXPORT_SYMBOL(v4l2_ctrl_new_int_menu); + +/* Add a control from another handler to this handler */ +struct v4l2_ctrl *v4l2_ctrl_add_ctrl(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl *ctrl) +{ + if (hdl == NULL || hdl->error) + return NULL; + if (ctrl == NULL) { + handler_set_err(hdl, -EINVAL); + return NULL; + } + if (ctrl->handler == hdl) + return ctrl; + return handler_new_ref(hdl, ctrl) ? NULL : ctrl; +} +EXPORT_SYMBOL(v4l2_ctrl_add_ctrl); + +/* Add the controls from another handler to our own. */ +int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, + struct v4l2_ctrl_handler *add) +{ + struct v4l2_ctrl_ref *ref; + int ret = 0; + + /* Do nothing if either handler is NULL or if they are the same */ + if (!hdl || !add || hdl == add) + return 0; + if (hdl->error) + return hdl->error; + mutex_lock(add->lock); + list_for_each_entry(ref, &add->ctrl_refs, node) { + struct v4l2_ctrl *ctrl = ref->ctrl; + + /* Skip handler-private controls. */ + if (ctrl->is_private) + continue; + /* And control classes */ + if (ctrl->type == V4L2_CTRL_TYPE_CTRL_CLASS) + continue; + ret = handler_new_ref(hdl, ctrl); + if (ret) + break; + } + mutex_unlock(add->lock); + return ret; +} +EXPORT_SYMBOL(v4l2_ctrl_add_handler); + +/* Cluster controls */ +void v4l2_ctrl_cluster(unsigned ncontrols, struct v4l2_ctrl **controls) +{ + bool has_volatiles = false; + int i; + + /* The first control is the master control and it must not be NULL */ + BUG_ON(ncontrols == 0 || controls[0] == NULL); + + for (i = 0; i < ncontrols; i++) { + if (controls[i]) { + controls[i]->cluster = controls; + controls[i]->ncontrols = ncontrols; + if (controls[i]->flags & V4L2_CTRL_FLAG_VOLATILE) + has_volatiles = true; + } + } + controls[0]->has_volatiles = has_volatiles; +} +EXPORT_SYMBOL(v4l2_ctrl_cluster); + +void v4l2_ctrl_auto_cluster(unsigned ncontrols, struct v4l2_ctrl **controls, + u8 manual_val, bool set_volatile) +{ + struct v4l2_ctrl *master = controls[0]; + u32 flag = 0; + int i; + + v4l2_ctrl_cluster(ncontrols, controls); + WARN_ON(ncontrols <= 1); + WARN_ON(manual_val < master->minimum || manual_val > master->maximum); + WARN_ON(set_volatile && !has_op(master, g_volatile_ctrl)); + master->is_auto = true; + master->has_volatiles = set_volatile; + master->manual_mode_value = manual_val; + master->flags |= V4L2_CTRL_FLAG_UPDATE; + + if (!is_cur_manual(master)) + flag = V4L2_CTRL_FLAG_INACTIVE | + (set_volatile ? V4L2_CTRL_FLAG_VOLATILE : 0); + + for (i = 1; i < ncontrols; i++) + if (controls[i]) + controls[i]->flags |= flag; +} +EXPORT_SYMBOL(v4l2_ctrl_auto_cluster); + +/* Activate/deactivate a control. */ +void v4l2_ctrl_activate(struct v4l2_ctrl *ctrl, bool active) +{ + /* invert since the actual flag is called 'inactive' */ + bool inactive = !active; + bool old; + + if (ctrl == NULL) + return; + + if (inactive) + /* set V4L2_CTRL_FLAG_INACTIVE */ + old = test_and_set_bit(4, &ctrl->flags); + else + /* clear V4L2_CTRL_FLAG_INACTIVE */ + old = test_and_clear_bit(4, &ctrl->flags); + if (old != inactive) + send_event(NULL, ctrl, V4L2_EVENT_CTRL_CH_FLAGS); +} +EXPORT_SYMBOL(v4l2_ctrl_activate); + +/* Grab/ungrab a control. + Typically used when streaming starts and you want to grab controls, + preventing the user from changing them. + + Just call this and the framework will block any attempts to change + these controls. */ +void v4l2_ctrl_grab(struct v4l2_ctrl *ctrl, bool grabbed) +{ + bool old; + + if (ctrl == NULL) + return; + + v4l2_ctrl_lock(ctrl); + if (grabbed) + /* set V4L2_CTRL_FLAG_GRABBED */ + old = test_and_set_bit(1, &ctrl->flags); + else + /* clear V4L2_CTRL_FLAG_GRABBED */ + old = test_and_clear_bit(1, &ctrl->flags); + if (old != grabbed) + send_event(NULL, ctrl, V4L2_EVENT_CTRL_CH_FLAGS); + v4l2_ctrl_unlock(ctrl); +} +EXPORT_SYMBOL(v4l2_ctrl_grab); + +/* Log the control name and value */ +static void log_ctrl(const struct v4l2_ctrl *ctrl, + const char *prefix, const char *colon) +{ + if (ctrl->flags & (V4L2_CTRL_FLAG_DISABLED | V4L2_CTRL_FLAG_WRITE_ONLY)) + return; + if (ctrl->type == V4L2_CTRL_TYPE_CTRL_CLASS) + return; + + printk(KERN_INFO "%s%s%s: ", prefix, colon, ctrl->name); + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_INTEGER: + printk(KERN_CONT "%d", ctrl->cur.val); + break; + case V4L2_CTRL_TYPE_BOOLEAN: + printk(KERN_CONT "%s", ctrl->cur.val ? "true" : "false"); + break; + case V4L2_CTRL_TYPE_MENU: + printk(KERN_CONT "%s", ctrl->qmenu[ctrl->cur.val]); + break; + case V4L2_CTRL_TYPE_INTEGER_MENU: + printk(KERN_CONT "%lld", ctrl->qmenu_int[ctrl->cur.val]); + break; + case V4L2_CTRL_TYPE_BITMASK: + printk(KERN_CONT "0x%08x", ctrl->cur.val); + break; + case V4L2_CTRL_TYPE_INTEGER64: + printk(KERN_CONT "%lld", ctrl->cur.val64); + break; + case V4L2_CTRL_TYPE_STRING: + printk(KERN_CONT "%s", ctrl->cur.string); + break; + default: + printk(KERN_CONT "unknown type %d", ctrl->type); + break; + } + if (ctrl->flags & (V4L2_CTRL_FLAG_INACTIVE | + V4L2_CTRL_FLAG_GRABBED | + V4L2_CTRL_FLAG_VOLATILE)) { + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + printk(KERN_CONT " inactive"); + if (ctrl->flags & V4L2_CTRL_FLAG_GRABBED) + printk(KERN_CONT " grabbed"); + if (ctrl->flags & V4L2_CTRL_FLAG_VOLATILE) + printk(KERN_CONT " volatile"); + } + printk(KERN_CONT "\n"); +} + +/* Log all controls owned by the handler */ +void v4l2_ctrl_handler_log_status(struct v4l2_ctrl_handler *hdl, + const char *prefix) +{ + struct v4l2_ctrl *ctrl; + const char *colon = ""; + int len; + + if (hdl == NULL) + return; + if (prefix == NULL) + prefix = ""; + len = strlen(prefix); + if (len && prefix[len - 1] != ' ') + colon = ": "; + mutex_lock(hdl->lock); + list_for_each_entry(ctrl, &hdl->ctrls, node) + if (!(ctrl->flags & V4L2_CTRL_FLAG_DISABLED)) + log_ctrl(ctrl, prefix, colon); + mutex_unlock(hdl->lock); +} +EXPORT_SYMBOL(v4l2_ctrl_handler_log_status); + +/* Call s_ctrl for all controls owned by the handler */ +int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl) +{ + struct v4l2_ctrl *ctrl; + int ret = 0; + + if (hdl == NULL) + return 0; + mutex_lock(hdl->lock); + list_for_each_entry(ctrl, &hdl->ctrls, node) + ctrl->done = false; + + list_for_each_entry(ctrl, &hdl->ctrls, node) { + struct v4l2_ctrl *master = ctrl->cluster[0]; + int i; + + /* Skip if this control was already handled by a cluster. */ + /* Skip button controls and read-only controls. */ + if (ctrl->done || ctrl->type == V4L2_CTRL_TYPE_BUTTON || + (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)) + continue; + + for (i = 0; i < master->ncontrols; i++) { + if (master->cluster[i]) { + cur_to_new(master->cluster[i]); + master->cluster[i]->is_new = 1; + master->cluster[i]->done = true; + } + } + ret = call_op(master, s_ctrl); + if (ret) + break; + } + mutex_unlock(hdl->lock); + return ret; +} +EXPORT_SYMBOL(v4l2_ctrl_handler_setup); + +/* Implement VIDIOC_QUERYCTRL */ +int v4l2_queryctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_queryctrl *qc) +{ + u32 id = qc->id & V4L2_CTRL_ID_MASK; + struct v4l2_ctrl_ref *ref; + struct v4l2_ctrl *ctrl; + + if (hdl == NULL) + return -EINVAL; + + mutex_lock(hdl->lock); + + /* Try to find it */ + ref = find_ref(hdl, id); + + if ((qc->id & V4L2_CTRL_FLAG_NEXT_CTRL) && !list_empty(&hdl->ctrl_refs)) { + /* Find the next control with ID > qc->id */ + + /* Did we reach the end of the control list? */ + if (id >= node2id(hdl->ctrl_refs.prev)) { + ref = NULL; /* Yes, so there is no next control */ + } else if (ref) { + /* We found a control with the given ID, so just get + the next one in the list. */ + ref = list_entry(ref->node.next, typeof(*ref), node); + } else { + /* No control with the given ID exists, so start + searching for the next largest ID. We know there + is one, otherwise the first 'if' above would have + been true. */ + list_for_each_entry(ref, &hdl->ctrl_refs, node) + if (id < ref->ctrl->id) + break; + } + } + mutex_unlock(hdl->lock); + if (!ref) + return -EINVAL; + + ctrl = ref->ctrl; + memset(qc, 0, sizeof(*qc)); + if (id >= V4L2_CID_PRIVATE_BASE) + qc->id = id; + else + qc->id = ctrl->id; + strlcpy(qc->name, ctrl->name, sizeof(qc->name)); + qc->minimum = ctrl->minimum; + qc->maximum = ctrl->maximum; + qc->default_value = ctrl->default_value; + if (ctrl->type == V4L2_CTRL_TYPE_MENU + || ctrl->type == V4L2_CTRL_TYPE_INTEGER_MENU) + qc->step = 1; + else + qc->step = ctrl->step; + qc->flags = ctrl->flags; + qc->type = ctrl->type; + return 0; +} +EXPORT_SYMBOL(v4l2_queryctrl); + +int v4l2_subdev_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) +{ + if (qc->id & V4L2_CTRL_FLAG_NEXT_CTRL) + return -EINVAL; + return v4l2_queryctrl(sd->ctrl_handler, qc); +} +EXPORT_SYMBOL(v4l2_subdev_queryctrl); + +/* Implement VIDIOC_QUERYMENU */ +int v4l2_querymenu(struct v4l2_ctrl_handler *hdl, struct v4l2_querymenu *qm) +{ + struct v4l2_ctrl *ctrl; + u32 i = qm->index; + + ctrl = v4l2_ctrl_find(hdl, qm->id); + if (!ctrl) + return -EINVAL; + + qm->reserved = 0; + /* Sanity checks */ + switch (ctrl->type) { + case V4L2_CTRL_TYPE_MENU: + if (ctrl->qmenu == NULL) + return -EINVAL; + break; + case V4L2_CTRL_TYPE_INTEGER_MENU: + if (ctrl->qmenu_int == NULL) + return -EINVAL; + break; + default: + return -EINVAL; + } + + if (i < ctrl->minimum || i > ctrl->maximum) + return -EINVAL; + + /* Use mask to see if this menu item should be skipped */ + if (ctrl->menu_skip_mask & (1 << i)) + return -EINVAL; + /* Empty menu items should also be skipped */ + if (ctrl->type == V4L2_CTRL_TYPE_MENU) { + if (ctrl->qmenu[i] == NULL || ctrl->qmenu[i][0] == '\0') + return -EINVAL; + strlcpy(qm->name, ctrl->qmenu[i], sizeof(qm->name)); + } else { + qm->value = ctrl->qmenu_int[i]; + } + return 0; +} +EXPORT_SYMBOL(v4l2_querymenu); + +int v4l2_subdev_querymenu(struct v4l2_subdev *sd, struct v4l2_querymenu *qm) +{ + return v4l2_querymenu(sd->ctrl_handler, qm); +} +EXPORT_SYMBOL(v4l2_subdev_querymenu); + + + +/* Some general notes on the atomic requirements of VIDIOC_G/TRY/S_EXT_CTRLS: + + It is not a fully atomic operation, just best-effort only. After all, if + multiple controls have to be set through multiple i2c writes (for example) + then some initial writes may succeed while others fail. Thus leaving the + system in an inconsistent state. The question is how much effort you are + willing to spend on trying to make something atomic that really isn't. + + From the point of view of an application the main requirement is that + when you call VIDIOC_S_EXT_CTRLS and some values are invalid then an + error should be returned without actually affecting any controls. + + If all the values are correct, then it is acceptable to just give up + in case of low-level errors. + + It is important though that the application can tell when only a partial + configuration was done. The way we do that is through the error_idx field + of struct v4l2_ext_controls: if that is equal to the count field then no + controls were affected. Otherwise all controls before that index were + successful in performing their 'get' or 'set' operation, the control at + the given index failed, and you don't know what happened with the controls + after the failed one. Since if they were part of a control cluster they + could have been successfully processed (if a cluster member was encountered + at index < error_idx), they could have failed (if a cluster member was at + error_idx), or they may not have been processed yet (if the first cluster + member appeared after error_idx). + + It is all fairly theoretical, though. In practice all you can do is to + bail out. If error_idx == count, then it is an application bug. If + error_idx < count then it is only an application bug if the error code was + EBUSY. That usually means that something started streaming just when you + tried to set the controls. In all other cases it is a driver/hardware + problem and all you can do is to retry or bail out. + + Note that these rules do not apply to VIDIOC_TRY_EXT_CTRLS: since that + never modifies controls the error_idx is just set to whatever control + has an invalid value. + */ + +/* Prepare for the extended g/s/try functions. + Find the controls in the control array and do some basic checks. */ +static int prepare_ext_ctrls(struct v4l2_ctrl_handler *hdl, + struct v4l2_ext_controls *cs, + struct v4l2_ctrl_helper *helpers) +{ + struct v4l2_ctrl_helper *h; + bool have_clusters = false; + u32 i; + + for (i = 0, h = helpers; i < cs->count; i++, h++) { + struct v4l2_ext_control *c = &cs->controls[i]; + struct v4l2_ctrl_ref *ref; + struct v4l2_ctrl *ctrl; + u32 id = c->id & V4L2_CTRL_ID_MASK; + + cs->error_idx = i; + + if (cs->ctrl_class && V4L2_CTRL_ID2CLASS(id) != cs->ctrl_class) + return -EINVAL; + + /* Old-style private controls are not allowed for + extended controls */ + if (id >= V4L2_CID_PRIVATE_BASE) + return -EINVAL; + ref = find_ref_lock(hdl, id); + if (ref == NULL) + return -EINVAL; + ctrl = ref->ctrl; + if (ctrl->flags & V4L2_CTRL_FLAG_DISABLED) + return -EINVAL; + + if (ctrl->cluster[0]->ncontrols > 1) + have_clusters = true; + if (ctrl->cluster[0] != ctrl) + ref = find_ref_lock(hdl, ctrl->cluster[0]->id); + /* Store the ref to the master control of the cluster */ + h->mref = ref; + h->ctrl = ctrl; + /* Initially set next to 0, meaning that there is no other + control in this helper array belonging to the same + cluster */ + h->next = 0; + } + + /* We are done if there were no controls that belong to a multi- + control cluster. */ + if (!have_clusters) + return 0; + + /* The code below figures out in O(n) time which controls in the list + belong to the same cluster. */ + + /* This has to be done with the handler lock taken. */ + mutex_lock(hdl->lock); + + /* First zero the helper field in the master control references */ + for (i = 0; i < cs->count; i++) + helpers[i].mref->helper = NULL; + for (i = 0, h = helpers; i < cs->count; i++, h++) { + struct v4l2_ctrl_ref *mref = h->mref; + + /* If the mref->helper is set, then it points to an earlier + helper that belongs to the same cluster. */ + if (mref->helper) { + /* Set the next field of mref->helper to the current + index: this means that that earlier helper now + points to the next helper in the same cluster. */ + mref->helper->next = i; + /* mref should be set only for the first helper in the + cluster, clear the others. */ + h->mref = NULL; + } + /* Point the mref helper to the current helper struct. */ + mref->helper = h; + } + mutex_unlock(hdl->lock); + return 0; +} + +/* Handles the corner case where cs->count == 0. It checks whether the + specified control class exists. If that class ID is 0, then it checks + whether there are any controls at all. */ +static int class_check(struct v4l2_ctrl_handler *hdl, u32 ctrl_class) +{ + if (ctrl_class == 0) + return list_empty(&hdl->ctrl_refs) ? -EINVAL : 0; + return find_ref_lock(hdl, ctrl_class | 1) ? 0 : -EINVAL; +} + + + +/* Get extended controls. Allocates the helpers array if needed. */ +int v4l2_g_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs) +{ + struct v4l2_ctrl_helper helper[4]; + struct v4l2_ctrl_helper *helpers = helper; + int ret; + int i, j; + + cs->error_idx = cs->count; + cs->ctrl_class = V4L2_CTRL_ID2CLASS(cs->ctrl_class); + + if (hdl == NULL) + return -EINVAL; + + if (cs->count == 0) + return class_check(hdl, cs->ctrl_class); + + if (cs->count > ARRAY_SIZE(helper)) { + helpers = kmalloc_array(cs->count, sizeof(helper[0]), + GFP_KERNEL); + if (helpers == NULL) + return -ENOMEM; + } + + ret = prepare_ext_ctrls(hdl, cs, helpers); + cs->error_idx = cs->count; + + for (i = 0; !ret && i < cs->count; i++) + if (helpers[i].ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY) + ret = -EACCES; + + for (i = 0; !ret && i < cs->count; i++) { + int (*ctrl_to_user)(struct v4l2_ext_control *c, + struct v4l2_ctrl *ctrl) = cur_to_user; + struct v4l2_ctrl *master; + + if (helpers[i].mref == NULL) + continue; + + master = helpers[i].mref->ctrl; + cs->error_idx = i; + + v4l2_ctrl_lock(master); + + /* g_volatile_ctrl will update the new control values */ + if ((master->flags & V4L2_CTRL_FLAG_VOLATILE) || + (master->has_volatiles && !is_cur_manual(master))) { + for (j = 0; j < master->ncontrols; j++) + cur_to_new(master->cluster[j]); + ret = call_op(master, g_volatile_ctrl); + ctrl_to_user = new_to_user; + } + /* If OK, then copy the current (for non-volatile controls) + or the new (for volatile controls) control values to the + caller */ + if (!ret) { + u32 idx = i; + + do { + ret = ctrl_to_user(cs->controls + idx, + helpers[idx].ctrl); + idx = helpers[idx].next; + } while (!ret && idx); + } + v4l2_ctrl_unlock(master); + } + + if (cs->count > ARRAY_SIZE(helper)) + kfree(helpers); + return ret; +} +EXPORT_SYMBOL(v4l2_g_ext_ctrls); + +int v4l2_subdev_g_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs) +{ + return v4l2_g_ext_ctrls(sd->ctrl_handler, cs); +} +EXPORT_SYMBOL(v4l2_subdev_g_ext_ctrls); + +/* Helper function to get a single control */ +static int get_ctrl(struct v4l2_ctrl *ctrl, s32 *val) +{ + struct v4l2_ctrl *master = ctrl->cluster[0]; + int ret = 0; + int i; + + if (ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY) + return -EACCES; + + v4l2_ctrl_lock(master); + /* g_volatile_ctrl will update the current control values */ + if (ctrl->flags & V4L2_CTRL_FLAG_VOLATILE) { + for (i = 0; i < master->ncontrols; i++) + cur_to_new(master->cluster[i]); + ret = call_op(master, g_volatile_ctrl); + *val = ctrl->val; + } else { + *val = ctrl->cur.val; + } + v4l2_ctrl_unlock(master); + return ret; +} + +int v4l2_g_ctrl(struct v4l2_ctrl_handler *hdl, struct v4l2_control *control) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(hdl, control->id); + + if (ctrl == NULL || !type_is_int(ctrl)) + return -EINVAL; + return get_ctrl(ctrl, &control->value); +} +EXPORT_SYMBOL(v4l2_g_ctrl); + +int v4l2_subdev_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *control) +{ + return v4l2_g_ctrl(sd->ctrl_handler, control); +} +EXPORT_SYMBOL(v4l2_subdev_g_ctrl); + +s32 v4l2_ctrl_g_ctrl(struct v4l2_ctrl *ctrl) +{ + s32 val = 0; + + /* It's a driver bug if this happens. */ + WARN_ON(!type_is_int(ctrl)); + get_ctrl(ctrl, &val); + return val; +} +EXPORT_SYMBOL(v4l2_ctrl_g_ctrl); + + +/* Core function that calls try/s_ctrl and ensures that the new value is + copied to the current value on a set. + Must be called with ctrl->handler->lock held. */ +static int try_or_set_cluster(struct v4l2_fh *fh, + struct v4l2_ctrl *master, bool set) +{ + bool update_flag; + int ret; + int i; + + /* Go through the cluster and either validate the new value or + (if no new value was set), copy the current value to the new + value, ensuring a consistent view for the control ops when + called. */ + for (i = 0; i < master->ncontrols; i++) { + struct v4l2_ctrl *ctrl = master->cluster[i]; + + if (ctrl == NULL) + continue; + + if (!ctrl->is_new) { + cur_to_new(ctrl); + continue; + } + /* Check again: it may have changed since the + previous check in try_or_set_ext_ctrls(). */ + if (set && (ctrl->flags & V4L2_CTRL_FLAG_GRABBED)) + return -EBUSY; + } + + ret = call_op(master, try_ctrl); + + /* Don't set if there is no change */ + if (ret || !set || !cluster_changed(master)) + return ret; + ret = call_op(master, s_ctrl); + if (ret) + return ret; + + /* If OK, then make the new values permanent. */ + update_flag = is_cur_manual(master) != is_new_manual(master); + for (i = 0; i < master->ncontrols; i++) + new_to_cur(fh, master->cluster[i], update_flag && i > 0); + return 0; +} + +/* Validate controls. */ +static int validate_ctrls(struct v4l2_ext_controls *cs, + struct v4l2_ctrl_helper *helpers, bool set) +{ + unsigned i; + int ret = 0; + + cs->error_idx = cs->count; + for (i = 0; i < cs->count; i++) { + struct v4l2_ctrl *ctrl = helpers[i].ctrl; + + cs->error_idx = i; + + if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) + return -EACCES; + /* This test is also done in try_set_control_cluster() which + is called in atomic context, so that has the final say, + but it makes sense to do an up-front check as well. Once + an error occurs in try_set_control_cluster() some other + controls may have been set already and we want to do a + best-effort to avoid that. */ + if (set && (ctrl->flags & V4L2_CTRL_FLAG_GRABBED)) + return -EBUSY; + ret = validate_new(ctrl, &cs->controls[i]); + if (ret) + return ret; + } + return 0; +} + +/* Obtain the current volatile values of an autocluster and mark them + as new. */ +static void update_from_auto_cluster(struct v4l2_ctrl *master) +{ + int i; + + for (i = 0; i < master->ncontrols; i++) + cur_to_new(master->cluster[i]); + if (!call_op(master, g_volatile_ctrl)) + for (i = 1; i < master->ncontrols; i++) + if (master->cluster[i]) + master->cluster[i]->is_new = 1; +} + +/* Try or try-and-set controls */ +static int try_set_ext_ctrls(struct v4l2_fh *fh, struct v4l2_ctrl_handler *hdl, + struct v4l2_ext_controls *cs, + bool set) +{ + struct v4l2_ctrl_helper helper[4]; + struct v4l2_ctrl_helper *helpers = helper; + unsigned i, j; + int ret; + + cs->error_idx = cs->count; + cs->ctrl_class = V4L2_CTRL_ID2CLASS(cs->ctrl_class); + + if (hdl == NULL) + return -EINVAL; + + if (cs->count == 0) + return class_check(hdl, cs->ctrl_class); + + if (cs->count > ARRAY_SIZE(helper)) { + helpers = kmalloc_array(cs->count, sizeof(helper[0]), + GFP_KERNEL); + if (!helpers) + return -ENOMEM; + } + ret = prepare_ext_ctrls(hdl, cs, helpers); + if (!ret) + ret = validate_ctrls(cs, helpers, set); + if (ret && set) + cs->error_idx = cs->count; + for (i = 0; !ret && i < cs->count; i++) { + struct v4l2_ctrl *master; + u32 idx = i; + + if (helpers[i].mref == NULL) + continue; + + cs->error_idx = i; + master = helpers[i].mref->ctrl; + v4l2_ctrl_lock(master); + + /* Reset the 'is_new' flags of the cluster */ + for (j = 0; j < master->ncontrols; j++) + if (master->cluster[j]) + master->cluster[j]->is_new = 0; + + /* For volatile autoclusters that are currently in auto mode + we need to discover if it will be set to manual mode. + If so, then we have to copy the current volatile values + first since those will become the new manual values (which + may be overwritten by explicit new values from this set + of controls). */ + if (master->is_auto && master->has_volatiles && + !is_cur_manual(master)) { + /* Pick an initial non-manual value */ + s32 new_auto_val = master->manual_mode_value + 1; + u32 tmp_idx = idx; + + do { + /* Check if the auto control is part of the + list, and remember the new value. */ + if (helpers[tmp_idx].ctrl == master) + new_auto_val = cs->controls[tmp_idx].value; + tmp_idx = helpers[tmp_idx].next; + } while (tmp_idx); + /* If the new value == the manual value, then copy + the current volatile values. */ + if (new_auto_val == master->manual_mode_value) + update_from_auto_cluster(master); + } + + /* Copy the new caller-supplied control values. + user_to_new() sets 'is_new' to 1. */ + do { + ret = user_to_new(cs->controls + idx, helpers[idx].ctrl); + idx = helpers[idx].next; + } while (!ret && idx); + + if (!ret) + ret = try_or_set_cluster(fh, master, set); + + /* Copy the new values back to userspace. */ + if (!ret) { + idx = i; + do { + ret = new_to_user(cs->controls + idx, + helpers[idx].ctrl); + idx = helpers[idx].next; + } while (!ret && idx); + } + v4l2_ctrl_unlock(master); + } + + if (cs->count > ARRAY_SIZE(helper)) + kfree(helpers); + return ret; +} + +int v4l2_try_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(NULL, hdl, cs, false); +} +EXPORT_SYMBOL(v4l2_try_ext_ctrls); + +int v4l2_s_ext_ctrls(struct v4l2_fh *fh, struct v4l2_ctrl_handler *hdl, + struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(fh, hdl, cs, true); +} +EXPORT_SYMBOL(v4l2_s_ext_ctrls); + +int v4l2_subdev_try_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(NULL, sd->ctrl_handler, cs, false); +} +EXPORT_SYMBOL(v4l2_subdev_try_ext_ctrls); + +int v4l2_subdev_s_ext_ctrls(struct v4l2_subdev *sd, struct v4l2_ext_controls *cs) +{ + return try_set_ext_ctrls(NULL, sd->ctrl_handler, cs, true); +} +EXPORT_SYMBOL(v4l2_subdev_s_ext_ctrls); + +/* Helper function for VIDIOC_S_CTRL compatibility */ +static int set_ctrl(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, s32 *val) +{ + struct v4l2_ctrl *master = ctrl->cluster[0]; + int ret; + int i; + + ret = validate_new_int(ctrl, val); + if (ret) + return ret; + + v4l2_ctrl_lock(ctrl); + + /* Reset the 'is_new' flags of the cluster */ + for (i = 0; i < master->ncontrols; i++) + if (master->cluster[i]) + master->cluster[i]->is_new = 0; + + /* For autoclusters with volatiles that are switched from auto to + manual mode we have to update the current volatile values since + those will become the initial manual values after such a switch. */ + if (master->is_auto && master->has_volatiles && ctrl == master && + !is_cur_manual(master) && *val == master->manual_mode_value) + update_from_auto_cluster(master); + ctrl->val = *val; + ctrl->is_new = 1; + ret = try_or_set_cluster(fh, master, true); + *val = ctrl->cur.val; + v4l2_ctrl_unlock(ctrl); + return ret; +} + +int v4l2_s_ctrl(struct v4l2_fh *fh, struct v4l2_ctrl_handler *hdl, + struct v4l2_control *control) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(hdl, control->id); + + if (ctrl == NULL || !type_is_int(ctrl)) + return -EINVAL; + + if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) + return -EACCES; + + return set_ctrl(fh, ctrl, &control->value); +} +EXPORT_SYMBOL(v4l2_s_ctrl); + +int v4l2_subdev_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *control) +{ + return v4l2_s_ctrl(NULL, sd->ctrl_handler, control); +} +EXPORT_SYMBOL(v4l2_subdev_s_ctrl); + +int v4l2_ctrl_s_ctrl(struct v4l2_ctrl *ctrl, s32 val) +{ + /* It's a driver bug if this happens. */ + WARN_ON(!type_is_int(ctrl)); + return set_ctrl(NULL, ctrl, &val); +} +EXPORT_SYMBOL(v4l2_ctrl_s_ctrl); + +static int v4l2_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(sev->fh->ctrl_handler, sev->id); + + if (ctrl == NULL) + return -EINVAL; + + v4l2_ctrl_lock(ctrl); + list_add_tail(&sev->node, &ctrl->ev_subs); + if (ctrl->type != V4L2_CTRL_TYPE_CTRL_CLASS && + (sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL)) { + struct v4l2_event ev; + u32 changes = V4L2_EVENT_CTRL_CH_FLAGS; + + if (!(ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY)) + changes |= V4L2_EVENT_CTRL_CH_VALUE; + fill_event(&ev, ctrl, changes); + /* Mark the queue as active, allowing this initial + event to be accepted. */ + sev->elems = elems; + v4l2_event_queue_fh(sev->fh, &ev); + } + v4l2_ctrl_unlock(ctrl); + return 0; +} + +static void v4l2_ctrl_del_event(struct v4l2_subscribed_event *sev) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(sev->fh->ctrl_handler, sev->id); + + v4l2_ctrl_lock(ctrl); + list_del(&sev->node); + v4l2_ctrl_unlock(ctrl); +} + +void v4l2_ctrl_replace(struct v4l2_event *old, const struct v4l2_event *new) +{ + u32 old_changes = old->u.ctrl.changes; + + old->u.ctrl = new->u.ctrl; + old->u.ctrl.changes |= old_changes; +} +EXPORT_SYMBOL(v4l2_ctrl_replace); + +void v4l2_ctrl_merge(const struct v4l2_event *old, struct v4l2_event *new) +{ + new->u.ctrl.changes |= old->u.ctrl.changes; +} +EXPORT_SYMBOL(v4l2_ctrl_merge); + +const struct v4l2_subscribed_event_ops v4l2_ctrl_sub_ev_ops = { + .add = v4l2_ctrl_add_event, + .del = v4l2_ctrl_del_event, + .replace = v4l2_ctrl_replace, + .merge = v4l2_ctrl_merge, +}; +EXPORT_SYMBOL(v4l2_ctrl_sub_ev_ops); + +int v4l2_ctrl_log_status(struct file *file, void *fh) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_fh *vfh = file->private_data; + + if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) && vfd->v4l2_dev) + v4l2_ctrl_handler_log_status(vfh->ctrl_handler, + vfd->v4l2_dev->name); + return 0; +} +EXPORT_SYMBOL(v4l2_ctrl_log_status); + +int v4l2_ctrl_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type == V4L2_EVENT_CTRL) + return v4l2_event_subscribe(fh, sub, 0, &v4l2_ctrl_sub_ev_ops); + return -EINVAL; +} +EXPORT_SYMBOL(v4l2_ctrl_subscribe_event); + +unsigned int v4l2_ctrl_poll(struct file *file, struct poll_table_struct *wait) +{ + struct v4l2_fh *fh = file->private_data; + + if (v4l2_event_pending(fh)) + return POLLPRI; + poll_wait(file, &fh->wait, wait); + return 0; +} +EXPORT_SYMBOL(v4l2_ctrl_poll); diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c new file mode 100644 index 000000000000..71237f5f85f4 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -0,0 +1,1003 @@ +/* + * Video capture interface for Linux version 2 + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * 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. + * + * Authors: Alan Cox, <alan@lxorguk.ukuu.org.uk> (version 1) + * Mauro Carvalho Chehab <mchehab@infradead.org> (version 2) + * + * Fixes: 20000516 Claudio Matsuoka <claudio@conectiva.com> + * - Added procfs support + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <asm/uaccess.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> + +#define VIDEO_NUM_DEVICES 256 +#define VIDEO_NAME "video4linux" + +/* + * sysfs stuff + */ + +static ssize_t show_index(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + + return sprintf(buf, "%i\n", vdev->index); +} + +static ssize_t show_debug(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + + return sprintf(buf, "%i\n", vdev->debug); +} + +static ssize_t set_debug(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct video_device *vdev = to_video_device(cd); + int res = 0; + u16 value; + + res = kstrtou16(buf, 0, &value); + if (res) + return res; + + vdev->debug = value; + return len; +} + +static ssize_t show_name(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + + return sprintf(buf, "%.*s\n", (int)sizeof(vdev->name), vdev->name); +} + +static struct device_attribute video_device_attrs[] = { + __ATTR(name, S_IRUGO, show_name, NULL), + __ATTR(debug, 0644, show_debug, set_debug), + __ATTR(index, S_IRUGO, show_index, NULL), + __ATTR_NULL +}; + +/* + * Active devices + */ +static struct video_device *video_device[VIDEO_NUM_DEVICES]; +static DEFINE_MUTEX(videodev_lock); +static DECLARE_BITMAP(devnode_nums[VFL_TYPE_MAX], VIDEO_NUM_DEVICES); + +/* Device node utility functions */ + +/* Note: these utility functions all assume that vfl_type is in the range + [0, VFL_TYPE_MAX-1]. */ + +#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES +/* Return the bitmap corresponding to vfl_type. */ +static inline unsigned long *devnode_bits(int vfl_type) +{ + /* Any types not assigned to fixed minor ranges must be mapped to + one single bitmap for the purposes of finding a free node number + since all those unassigned types use the same minor range. */ + int idx = (vfl_type > VFL_TYPE_RADIO) ? VFL_TYPE_MAX - 1 : vfl_type; + + return devnode_nums[idx]; +} +#else +/* Return the bitmap corresponding to vfl_type. */ +static inline unsigned long *devnode_bits(int vfl_type) +{ + return devnode_nums[vfl_type]; +} +#endif + +/* Mark device node number vdev->num as used */ +static inline void devnode_set(struct video_device *vdev) +{ + set_bit(vdev->num, devnode_bits(vdev->vfl_type)); +} + +/* Mark device node number vdev->num as unused */ +static inline void devnode_clear(struct video_device *vdev) +{ + clear_bit(vdev->num, devnode_bits(vdev->vfl_type)); +} + +/* Try to find a free device node number in the range [from, to> */ +static inline int devnode_find(struct video_device *vdev, int from, int to) +{ + return find_next_zero_bit(devnode_bits(vdev->vfl_type), to, from); +} + +struct video_device *video_device_alloc(void) +{ + return kzalloc(sizeof(struct video_device), GFP_KERNEL); +} +EXPORT_SYMBOL(video_device_alloc); + +void video_device_release(struct video_device *vdev) +{ + kfree(vdev); +} +EXPORT_SYMBOL(video_device_release); + +void video_device_release_empty(struct video_device *vdev) +{ + /* Do nothing */ + /* Only valid when the video_device struct is a static. */ +} +EXPORT_SYMBOL(video_device_release_empty); + +static inline void video_get(struct video_device *vdev) +{ + get_device(&vdev->dev); +} + +static inline void video_put(struct video_device *vdev) +{ + put_device(&vdev->dev); +} + +/* Called when the last user of the video device exits. */ +static void v4l2_device_release(struct device *cd) +{ + struct video_device *vdev = to_video_device(cd); + struct v4l2_device *v4l2_dev = vdev->v4l2_dev; + + mutex_lock(&videodev_lock); + if (WARN_ON(video_device[vdev->minor] != vdev)) { + /* should not happen */ + mutex_unlock(&videodev_lock); + return; + } + + /* Free up this device for reuse */ + video_device[vdev->minor] = NULL; + + /* Delete the cdev on this minor as well */ + cdev_del(vdev->cdev); + /* Just in case some driver tries to access this from + the release() callback. */ + vdev->cdev = NULL; + + /* Mark device node number as free */ + devnode_clear(vdev); + + mutex_unlock(&videodev_lock); + +#if defined(CONFIG_MEDIA_CONTROLLER) + if (v4l2_dev && v4l2_dev->mdev && + vdev->vfl_type != VFL_TYPE_SUBDEV) + media_device_unregister_entity(&vdev->entity); +#endif + + /* Do not call v4l2_device_put if there is no release callback set. + * Drivers that have no v4l2_device release callback might free the + * v4l2_dev instance in the video_device release callback below, so we + * must perform this check here. + * + * TODO: In the long run all drivers that use v4l2_device should use the + * v4l2_device release callback. This check will then be unnecessary. + */ + if (v4l2_dev && v4l2_dev->release == NULL) + v4l2_dev = NULL; + + /* Release video_device and perform other + cleanups as needed. */ + vdev->release(vdev); + + /* Decrease v4l2_device refcount */ + if (v4l2_dev) + v4l2_device_put(v4l2_dev); +} + +static struct class video_class = { + .name = VIDEO_NAME, + .dev_attrs = video_device_attrs, +}; + +struct video_device *video_devdata(struct file *file) +{ + return video_device[iminor(file->f_path.dentry->d_inode)]; +} +EXPORT_SYMBOL(video_devdata); + + +/* Priority handling */ + +static inline bool prio_is_valid(enum v4l2_priority prio) +{ + return prio == V4L2_PRIORITY_BACKGROUND || + prio == V4L2_PRIORITY_INTERACTIVE || + prio == V4L2_PRIORITY_RECORD; +} + +void v4l2_prio_init(struct v4l2_prio_state *global) +{ + memset(global, 0, sizeof(*global)); +} +EXPORT_SYMBOL(v4l2_prio_init); + +int v4l2_prio_change(struct v4l2_prio_state *global, enum v4l2_priority *local, + enum v4l2_priority new) +{ + if (!prio_is_valid(new)) + return -EINVAL; + if (*local == new) + return 0; + + atomic_inc(&global->prios[new]); + if (prio_is_valid(*local)) + atomic_dec(&global->prios[*local]); + *local = new; + return 0; +} +EXPORT_SYMBOL(v4l2_prio_change); + +void v4l2_prio_open(struct v4l2_prio_state *global, enum v4l2_priority *local) +{ + v4l2_prio_change(global, local, V4L2_PRIORITY_DEFAULT); +} +EXPORT_SYMBOL(v4l2_prio_open); + +void v4l2_prio_close(struct v4l2_prio_state *global, enum v4l2_priority local) +{ + if (prio_is_valid(local)) + atomic_dec(&global->prios[local]); +} +EXPORT_SYMBOL(v4l2_prio_close); + +enum v4l2_priority v4l2_prio_max(struct v4l2_prio_state *global) +{ + if (atomic_read(&global->prios[V4L2_PRIORITY_RECORD]) > 0) + return V4L2_PRIORITY_RECORD; + if (atomic_read(&global->prios[V4L2_PRIORITY_INTERACTIVE]) > 0) + return V4L2_PRIORITY_INTERACTIVE; + if (atomic_read(&global->prios[V4L2_PRIORITY_BACKGROUND]) > 0) + return V4L2_PRIORITY_BACKGROUND; + return V4L2_PRIORITY_UNSET; +} +EXPORT_SYMBOL(v4l2_prio_max); + +int v4l2_prio_check(struct v4l2_prio_state *global, enum v4l2_priority local) +{ + return (local < v4l2_prio_max(global)) ? -EBUSY : 0; +} +EXPORT_SYMBOL(v4l2_prio_check); + + +static ssize_t v4l2_read(struct file *filp, char __user *buf, + size_t sz, loff_t *off) +{ + struct video_device *vdev = video_devdata(filp); + int ret = -ENODEV; + + if (!vdev->fops->read) + return -EINVAL; + if (video_is_registered(vdev)) + ret = vdev->fops->read(filp, buf, sz, off); + if (vdev->debug) + printk(KERN_DEBUG "%s: read: %zd (%d)\n", + video_device_node_name(vdev), sz, ret); + return ret; +} + +static ssize_t v4l2_write(struct file *filp, const char __user *buf, + size_t sz, loff_t *off) +{ + struct video_device *vdev = video_devdata(filp); + int ret = -ENODEV; + + if (!vdev->fops->write) + return -EINVAL; + if (video_is_registered(vdev)) + ret = vdev->fops->write(filp, buf, sz, off); + if (vdev->debug) + printk(KERN_DEBUG "%s: write: %zd (%d)\n", + video_device_node_name(vdev), sz, ret); + return ret; +} + +static unsigned int v4l2_poll(struct file *filp, struct poll_table_struct *poll) +{ + struct video_device *vdev = video_devdata(filp); + unsigned int res = POLLERR | POLLHUP; + + if (!vdev->fops->poll) + return DEFAULT_POLLMASK; + if (video_is_registered(vdev)) + res = vdev->fops->poll(filp, poll); + if (vdev->debug) + printk(KERN_DEBUG "%s: poll: %08x\n", + video_device_node_name(vdev), res); + return res; +} + +static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct video_device *vdev = video_devdata(filp); + int ret = -ENODEV; + + if (vdev->fops->unlocked_ioctl) { + struct mutex *lock = v4l2_ioctl_get_lock(vdev, cmd); + + if (lock && mutex_lock_interruptible(lock)) + return -ERESTARTSYS; + if (video_is_registered(vdev)) + ret = vdev->fops->unlocked_ioctl(filp, cmd, arg); + if (lock) + mutex_unlock(lock); + } else if (vdev->fops->ioctl) { + /* This code path is a replacement for the BKL. It is a major + * hack but it will have to do for those drivers that are not + * yet converted to use unlocked_ioctl. + * + * There are two options: if the driver implements struct + * v4l2_device, then the lock defined there is used to + * serialize the ioctls. Otherwise the v4l2 core lock defined + * below is used. This lock is really bad since it serializes + * completely independent devices. + * + * Both variants suffer from the same problem: if the driver + * sleeps, then it blocks all ioctls since the lock is still + * held. This is very common for VIDIOC_DQBUF since that + * normally waits for a frame to arrive. As a result any other + * ioctl calls will proceed very, very slowly since each call + * will have to wait for the VIDIOC_QBUF to finish. Things that + * should take 0.01s may now take 10-20 seconds. + * + * The workaround is to *not* take the lock for VIDIOC_DQBUF. + * This actually works OK for videobuf-based drivers, since + * videobuf will take its own internal lock. + */ + static DEFINE_MUTEX(v4l2_ioctl_mutex); + struct mutex *m = vdev->v4l2_dev ? + &vdev->v4l2_dev->ioctl_lock : &v4l2_ioctl_mutex; + + if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m)) + return -ERESTARTSYS; + if (video_is_registered(vdev)) + ret = vdev->fops->ioctl(filp, cmd, arg); + if (cmd != VIDIOC_DQBUF) + mutex_unlock(m); + } else + ret = -ENOTTY; + + return ret; +} + +#ifdef CONFIG_MMU +#define v4l2_get_unmapped_area NULL +#else +static unsigned long v4l2_get_unmapped_area(struct file *filp, + unsigned long addr, unsigned long len, unsigned long pgoff, + unsigned long flags) +{ + struct video_device *vdev = video_devdata(filp); + int ret; + + if (!vdev->fops->get_unmapped_area) + return -ENOSYS; + if (!video_is_registered(vdev)) + return -ENODEV; + ret = vdev->fops->get_unmapped_area(filp, addr, len, pgoff, flags); + if (vdev->debug) + printk(KERN_DEBUG "%s: get_unmapped_area (%d)\n", + video_device_node_name(vdev), ret); + return ret; +} +#endif + +static int v4l2_mmap(struct file *filp, struct vm_area_struct *vm) +{ + struct video_device *vdev = video_devdata(filp); + int ret = -ENODEV; + + if (!vdev->fops->mmap) + return -ENODEV; + if (video_is_registered(vdev)) + ret = vdev->fops->mmap(filp, vm); + if (vdev->debug) + printk(KERN_DEBUG "%s: mmap (%d)\n", + video_device_node_name(vdev), ret); + return ret; +} + +/* Override for the open function */ +static int v4l2_open(struct inode *inode, struct file *filp) +{ + struct video_device *vdev; + int ret = 0; + + /* Check if the video device is available */ + mutex_lock(&videodev_lock); + vdev = video_devdata(filp); + /* return ENODEV if the video device has already been removed. */ + if (vdev == NULL || !video_is_registered(vdev)) { + mutex_unlock(&videodev_lock); + return -ENODEV; + } + /* and increase the device refcount */ + video_get(vdev); + mutex_unlock(&videodev_lock); + if (vdev->fops->open) { + if (video_is_registered(vdev)) + ret = vdev->fops->open(filp); + else + ret = -ENODEV; + } + + if (vdev->debug) + printk(KERN_DEBUG "%s: open (%d)\n", + video_device_node_name(vdev), ret); + /* decrease the refcount in case of an error */ + if (ret) + video_put(vdev); + return ret; +} + +/* Override for the release function */ +static int v4l2_release(struct inode *inode, struct file *filp) +{ + struct video_device *vdev = video_devdata(filp); + int ret = 0; + + if (vdev->fops->release) + ret = vdev->fops->release(filp); + if (vdev->debug) + printk(KERN_DEBUG "%s: release\n", + video_device_node_name(vdev)); + + /* decrease the refcount unconditionally since the release() + return value is ignored. */ + video_put(vdev); + return ret; +} + +static const struct file_operations v4l2_fops = { + .owner = THIS_MODULE, + .read = v4l2_read, + .write = v4l2_write, + .open = v4l2_open, + .get_unmapped_area = v4l2_get_unmapped_area, + .mmap = v4l2_mmap, + .unlocked_ioctl = v4l2_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = v4l2_compat_ioctl32, +#endif + .release = v4l2_release, + .poll = v4l2_poll, + .llseek = no_llseek, +}; + +/** + * get_index - assign stream index number based on parent device + * @vdev: video_device to assign index number to, vdev->parent should be assigned + * + * Note that when this is called the new device has not yet been registered + * in the video_device array, but it was able to obtain a minor number. + * + * This means that we can always obtain a free stream index number since + * the worst case scenario is that there are VIDEO_NUM_DEVICES - 1 slots in + * use of the video_device array. + * + * Returns a free index number. + */ +static int get_index(struct video_device *vdev) +{ + /* This can be static since this function is called with the global + videodev_lock held. */ + static DECLARE_BITMAP(used, VIDEO_NUM_DEVICES); + int i; + + /* Some drivers do not set the parent. In that case always return 0. */ + if (vdev->parent == NULL) + return 0; + + bitmap_zero(used, VIDEO_NUM_DEVICES); + + for (i = 0; i < VIDEO_NUM_DEVICES; i++) { + if (video_device[i] != NULL && + video_device[i]->parent == vdev->parent) { + set_bit(video_device[i]->index, used); + } + } + + return find_first_zero_bit(used, VIDEO_NUM_DEVICES); +} + +#define SET_VALID_IOCTL(ops, cmd, op) \ + if (ops->op) \ + set_bit(_IOC_NR(cmd), valid_ioctls) + +/* This determines which ioctls are actually implemented in the driver. + It's a one-time thing which simplifies video_ioctl2 as it can just do + a bit test. + + Note that drivers can override this by setting bits to 1 in + vdev->valid_ioctls. If an ioctl is marked as 1 when this function is + called, then that ioctl will actually be marked as unimplemented. + + It does that by first setting up the local valid_ioctls bitmap, and + at the end do a: + + vdev->valid_ioctls = valid_ioctls & ~(vdev->valid_ioctls) + */ +static void determine_valid_ioctls(struct video_device *vdev) +{ + DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE); + const struct v4l2_ioctl_ops *ops = vdev->ioctl_ops; + + bitmap_zero(valid_ioctls, BASE_VIDIOC_PRIVATE); + + SET_VALID_IOCTL(ops, VIDIOC_QUERYCAP, vidioc_querycap); + if (ops->vidioc_g_priority || + test_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags)) + set_bit(_IOC_NR(VIDIOC_G_PRIORITY), valid_ioctls); + if (ops->vidioc_s_priority || + test_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags)) + set_bit(_IOC_NR(VIDIOC_S_PRIORITY), valid_ioctls); + if (ops->vidioc_enum_fmt_vid_cap || + ops->vidioc_enum_fmt_vid_out || + ops->vidioc_enum_fmt_vid_cap_mplane || + ops->vidioc_enum_fmt_vid_out_mplane || + ops->vidioc_enum_fmt_vid_overlay || + ops->vidioc_enum_fmt_type_private) + set_bit(_IOC_NR(VIDIOC_ENUM_FMT), valid_ioctls); + if (ops->vidioc_g_fmt_vid_cap || + ops->vidioc_g_fmt_vid_out || + ops->vidioc_g_fmt_vid_cap_mplane || + ops->vidioc_g_fmt_vid_out_mplane || + ops->vidioc_g_fmt_vid_overlay || + ops->vidioc_g_fmt_vbi_cap || + ops->vidioc_g_fmt_vid_out_overlay || + ops->vidioc_g_fmt_vbi_out || + ops->vidioc_g_fmt_sliced_vbi_cap || + ops->vidioc_g_fmt_sliced_vbi_out || + ops->vidioc_g_fmt_type_private) + set_bit(_IOC_NR(VIDIOC_G_FMT), valid_ioctls); + if (ops->vidioc_s_fmt_vid_cap || + ops->vidioc_s_fmt_vid_out || + ops->vidioc_s_fmt_vid_cap_mplane || + ops->vidioc_s_fmt_vid_out_mplane || + ops->vidioc_s_fmt_vid_overlay || + ops->vidioc_s_fmt_vbi_cap || + ops->vidioc_s_fmt_vid_out_overlay || + ops->vidioc_s_fmt_vbi_out || + ops->vidioc_s_fmt_sliced_vbi_cap || + ops->vidioc_s_fmt_sliced_vbi_out || + ops->vidioc_s_fmt_type_private) + set_bit(_IOC_NR(VIDIOC_S_FMT), valid_ioctls); + if (ops->vidioc_try_fmt_vid_cap || + ops->vidioc_try_fmt_vid_out || + ops->vidioc_try_fmt_vid_cap_mplane || + ops->vidioc_try_fmt_vid_out_mplane || + ops->vidioc_try_fmt_vid_overlay || + ops->vidioc_try_fmt_vbi_cap || + ops->vidioc_try_fmt_vid_out_overlay || + ops->vidioc_try_fmt_vbi_out || + ops->vidioc_try_fmt_sliced_vbi_cap || + ops->vidioc_try_fmt_sliced_vbi_out || + ops->vidioc_try_fmt_type_private) + set_bit(_IOC_NR(VIDIOC_TRY_FMT), valid_ioctls); + SET_VALID_IOCTL(ops, VIDIOC_REQBUFS, vidioc_reqbufs); + SET_VALID_IOCTL(ops, VIDIOC_QUERYBUF, vidioc_querybuf); + SET_VALID_IOCTL(ops, VIDIOC_QBUF, vidioc_qbuf); + SET_VALID_IOCTL(ops, VIDIOC_DQBUF, vidioc_dqbuf); + SET_VALID_IOCTL(ops, VIDIOC_OVERLAY, vidioc_overlay); + SET_VALID_IOCTL(ops, VIDIOC_G_FBUF, vidioc_g_fbuf); + SET_VALID_IOCTL(ops, VIDIOC_S_FBUF, vidioc_s_fbuf); + SET_VALID_IOCTL(ops, VIDIOC_STREAMON, vidioc_streamon); + SET_VALID_IOCTL(ops, VIDIOC_STREAMOFF, vidioc_streamoff); + if (vdev->tvnorms) + set_bit(_IOC_NR(VIDIOC_ENUMSTD), valid_ioctls); + if (ops->vidioc_g_std || vdev->current_norm) + set_bit(_IOC_NR(VIDIOC_G_STD), valid_ioctls); + SET_VALID_IOCTL(ops, VIDIOC_S_STD, vidioc_s_std); + SET_VALID_IOCTL(ops, VIDIOC_QUERYSTD, vidioc_querystd); + SET_VALID_IOCTL(ops, VIDIOC_ENUMINPUT, vidioc_enum_input); + SET_VALID_IOCTL(ops, VIDIOC_G_INPUT, vidioc_g_input); + SET_VALID_IOCTL(ops, VIDIOC_S_INPUT, vidioc_s_input); + SET_VALID_IOCTL(ops, VIDIOC_ENUMOUTPUT, vidioc_enum_output); + SET_VALID_IOCTL(ops, VIDIOC_G_OUTPUT, vidioc_g_output); + SET_VALID_IOCTL(ops, VIDIOC_S_OUTPUT, vidioc_s_output); + /* Note: the control handler can also be passed through the filehandle, + and that can't be tested here. If the bit for these control ioctls + is set, then the ioctl is valid. But if it is 0, then it can still + be valid if the filehandle passed the control handler. */ + if (vdev->ctrl_handler || ops->vidioc_queryctrl) + set_bit(_IOC_NR(VIDIOC_QUERYCTRL), valid_ioctls); + if (vdev->ctrl_handler || ops->vidioc_g_ctrl || ops->vidioc_g_ext_ctrls) + set_bit(_IOC_NR(VIDIOC_G_CTRL), valid_ioctls); + if (vdev->ctrl_handler || ops->vidioc_s_ctrl || ops->vidioc_s_ext_ctrls) + set_bit(_IOC_NR(VIDIOC_S_CTRL), valid_ioctls); + if (vdev->ctrl_handler || ops->vidioc_g_ext_ctrls) + set_bit(_IOC_NR(VIDIOC_G_EXT_CTRLS), valid_ioctls); + if (vdev->ctrl_handler || ops->vidioc_s_ext_ctrls) + set_bit(_IOC_NR(VIDIOC_S_EXT_CTRLS), valid_ioctls); + if (vdev->ctrl_handler || ops->vidioc_try_ext_ctrls) + set_bit(_IOC_NR(VIDIOC_TRY_EXT_CTRLS), valid_ioctls); + if (vdev->ctrl_handler || ops->vidioc_querymenu) + set_bit(_IOC_NR(VIDIOC_QUERYMENU), valid_ioctls); + SET_VALID_IOCTL(ops, VIDIOC_ENUMAUDIO, vidioc_enumaudio); + SET_VALID_IOCTL(ops, VIDIOC_G_AUDIO, vidioc_g_audio); + SET_VALID_IOCTL(ops, VIDIOC_S_AUDIO, vidioc_s_audio); + SET_VALID_IOCTL(ops, VIDIOC_ENUMAUDOUT, vidioc_enumaudout); + SET_VALID_IOCTL(ops, VIDIOC_G_AUDOUT, vidioc_g_audout); + SET_VALID_IOCTL(ops, VIDIOC_S_AUDOUT, vidioc_s_audout); + SET_VALID_IOCTL(ops, VIDIOC_G_MODULATOR, vidioc_g_modulator); + SET_VALID_IOCTL(ops, VIDIOC_S_MODULATOR, vidioc_s_modulator); + if (ops->vidioc_g_crop || ops->vidioc_g_selection) + set_bit(_IOC_NR(VIDIOC_G_CROP), valid_ioctls); + if (ops->vidioc_s_crop || ops->vidioc_s_selection) + set_bit(_IOC_NR(VIDIOC_S_CROP), valid_ioctls); + SET_VALID_IOCTL(ops, VIDIOC_G_SELECTION, vidioc_g_selection); + SET_VALID_IOCTL(ops, VIDIOC_S_SELECTION, vidioc_s_selection); + if (ops->vidioc_cropcap || ops->vidioc_g_selection) + set_bit(_IOC_NR(VIDIOC_CROPCAP), valid_ioctls); + SET_VALID_IOCTL(ops, VIDIOC_G_JPEGCOMP, vidioc_g_jpegcomp); + SET_VALID_IOCTL(ops, VIDIOC_S_JPEGCOMP, vidioc_s_jpegcomp); + SET_VALID_IOCTL(ops, VIDIOC_G_ENC_INDEX, vidioc_g_enc_index); + SET_VALID_IOCTL(ops, VIDIOC_ENCODER_CMD, vidioc_encoder_cmd); + SET_VALID_IOCTL(ops, VIDIOC_TRY_ENCODER_CMD, vidioc_try_encoder_cmd); + SET_VALID_IOCTL(ops, VIDIOC_DECODER_CMD, vidioc_decoder_cmd); + SET_VALID_IOCTL(ops, VIDIOC_TRY_DECODER_CMD, vidioc_try_decoder_cmd); + if (ops->vidioc_g_parm || (vdev->vfl_type == VFL_TYPE_GRABBER && + (ops->vidioc_g_std || vdev->tvnorms))) + set_bit(_IOC_NR(VIDIOC_G_PARM), valid_ioctls); + SET_VALID_IOCTL(ops, VIDIOC_S_PARM, vidioc_s_parm); + SET_VALID_IOCTL(ops, VIDIOC_G_TUNER, vidioc_g_tuner); + SET_VALID_IOCTL(ops, VIDIOC_S_TUNER, vidioc_s_tuner); + SET_VALID_IOCTL(ops, VIDIOC_G_FREQUENCY, vidioc_g_frequency); + SET_VALID_IOCTL(ops, VIDIOC_S_FREQUENCY, vidioc_s_frequency); + SET_VALID_IOCTL(ops, VIDIOC_G_SLICED_VBI_CAP, vidioc_g_sliced_vbi_cap); + SET_VALID_IOCTL(ops, VIDIOC_LOG_STATUS, vidioc_log_status); +#ifdef CONFIG_VIDEO_ADV_DEBUG + SET_VALID_IOCTL(ops, VIDIOC_DBG_G_REGISTER, vidioc_g_register); + SET_VALID_IOCTL(ops, VIDIOC_DBG_S_REGISTER, vidioc_s_register); +#endif + SET_VALID_IOCTL(ops, VIDIOC_DBG_G_CHIP_IDENT, vidioc_g_chip_ident); + SET_VALID_IOCTL(ops, VIDIOC_S_HW_FREQ_SEEK, vidioc_s_hw_freq_seek); + SET_VALID_IOCTL(ops, VIDIOC_ENUM_FRAMESIZES, vidioc_enum_framesizes); + SET_VALID_IOCTL(ops, VIDIOC_ENUM_FRAMEINTERVALS, vidioc_enum_frameintervals); + SET_VALID_IOCTL(ops, VIDIOC_ENUM_DV_PRESETS, vidioc_enum_dv_presets); + SET_VALID_IOCTL(ops, VIDIOC_S_DV_PRESET, vidioc_s_dv_preset); + SET_VALID_IOCTL(ops, VIDIOC_G_DV_PRESET, vidioc_g_dv_preset); + SET_VALID_IOCTL(ops, VIDIOC_QUERY_DV_PRESET, vidioc_query_dv_preset); + SET_VALID_IOCTL(ops, VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings); + SET_VALID_IOCTL(ops, VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings); + SET_VALID_IOCTL(ops, VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings); + SET_VALID_IOCTL(ops, VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings); + SET_VALID_IOCTL(ops, VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap); + /* yes, really vidioc_subscribe_event */ + SET_VALID_IOCTL(ops, VIDIOC_DQEVENT, vidioc_subscribe_event); + SET_VALID_IOCTL(ops, VIDIOC_SUBSCRIBE_EVENT, vidioc_subscribe_event); + SET_VALID_IOCTL(ops, VIDIOC_UNSUBSCRIBE_EVENT, vidioc_unsubscribe_event); + SET_VALID_IOCTL(ops, VIDIOC_CREATE_BUFS, vidioc_create_bufs); + SET_VALID_IOCTL(ops, VIDIOC_PREPARE_BUF, vidioc_prepare_buf); + if (ops->vidioc_enum_freq_bands || ops->vidioc_g_tuner || ops->vidioc_g_modulator) + set_bit(_IOC_NR(VIDIOC_ENUM_FREQ_BANDS), valid_ioctls); + bitmap_andnot(vdev->valid_ioctls, valid_ioctls, vdev->valid_ioctls, + BASE_VIDIOC_PRIVATE); +} + +/** + * __video_register_device - register video4linux devices + * @vdev: video device structure we want to register + * @type: type of device to register + * @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ... + * -1 == first free) + * @warn_if_nr_in_use: warn if the desired device node number + * was already in use and another number was chosen instead. + * @owner: module that owns the video device node + * + * The registration code assigns minor numbers and device node numbers + * based on the requested type and registers the new device node with + * the kernel. + * + * This function assumes that struct video_device was zeroed when it + * was allocated and does not contain any stale date. + * + * An error is returned if no free minor or device node number could be + * found, or if the registration of the device node failed. + * + * Zero is returned on success. + * + * Valid types are + * + * %VFL_TYPE_GRABBER - A frame grabber + * + * %VFL_TYPE_VBI - Vertical blank data (undecoded) + * + * %VFL_TYPE_RADIO - A radio card + * + * %VFL_TYPE_SUBDEV - A subdevice + */ +int __video_register_device(struct video_device *vdev, int type, int nr, + int warn_if_nr_in_use, struct module *owner) +{ + int i = 0; + int ret; + int minor_offset = 0; + int minor_cnt = VIDEO_NUM_DEVICES; + const char *name_base; + + /* A minor value of -1 marks this video device as never + having been registered */ + vdev->minor = -1; + + /* the release callback MUST be present */ + if (WARN_ON(!vdev->release)) + return -EINVAL; + + /* v4l2_fh support */ + spin_lock_init(&vdev->fh_lock); + INIT_LIST_HEAD(&vdev->fh_list); + + /* Part 1: check device type */ + switch (type) { + case VFL_TYPE_GRABBER: + name_base = "video"; + break; + case VFL_TYPE_VBI: + name_base = "vbi"; + break; + case VFL_TYPE_RADIO: + name_base = "radio"; + break; + case VFL_TYPE_SUBDEV: + name_base = "v4l-subdev"; + break; + default: + printk(KERN_ERR "%s called with unknown type: %d\n", + __func__, type); + return -EINVAL; + } + + vdev->vfl_type = type; + vdev->cdev = NULL; + if (vdev->v4l2_dev) { + if (vdev->v4l2_dev->dev) + vdev->parent = vdev->v4l2_dev->dev; + if (vdev->ctrl_handler == NULL) + vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler; + /* If the prio state pointer is NULL, then use the v4l2_device + prio state. */ + if (vdev->prio == NULL) + vdev->prio = &vdev->v4l2_dev->prio; + } + + /* Part 2: find a free minor, device node number and device index. */ +#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES + /* Keep the ranges for the first four types for historical + * reasons. + * Newer devices (not yet in place) should use the range + * of 128-191 and just pick the first free minor there + * (new style). */ + switch (type) { + case VFL_TYPE_GRABBER: + minor_offset = 0; + minor_cnt = 64; + break; + case VFL_TYPE_RADIO: + minor_offset = 64; + minor_cnt = 64; + break; + case VFL_TYPE_VBI: + minor_offset = 224; + minor_cnt = 32; + break; + default: + minor_offset = 128; + minor_cnt = 64; + break; + } +#endif + + /* Pick a device node number */ + mutex_lock(&videodev_lock); + nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt); + if (nr == minor_cnt) + nr = devnode_find(vdev, 0, minor_cnt); + if (nr == minor_cnt) { + printk(KERN_ERR "could not get a free device node number\n"); + mutex_unlock(&videodev_lock); + return -ENFILE; + } +#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES + /* 1-on-1 mapping of device node number to minor number */ + i = nr; +#else + /* The device node number and minor numbers are independent, so + we just find the first free minor number. */ + for (i = 0; i < VIDEO_NUM_DEVICES; i++) + if (video_device[i] == NULL) + break; + if (i == VIDEO_NUM_DEVICES) { + mutex_unlock(&videodev_lock); + printk(KERN_ERR "could not get a free minor\n"); + return -ENFILE; + } +#endif + vdev->minor = i + minor_offset; + vdev->num = nr; + devnode_set(vdev); + + /* Should not happen since we thought this minor was free */ + WARN_ON(video_device[vdev->minor] != NULL); + vdev->index = get_index(vdev); + mutex_unlock(&videodev_lock); + + if (vdev->ioctl_ops) + determine_valid_ioctls(vdev); + + /* Part 3: Initialize the character device */ + vdev->cdev = cdev_alloc(); + if (vdev->cdev == NULL) { + ret = -ENOMEM; + goto cleanup; + } + vdev->cdev->ops = &v4l2_fops; + vdev->cdev->owner = owner; + ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); + if (ret < 0) { + printk(KERN_ERR "%s: cdev_add failed\n", __func__); + kfree(vdev->cdev); + vdev->cdev = NULL; + goto cleanup; + } + + /* Part 4: register the device with sysfs */ + vdev->dev.class = &video_class; + vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor); + if (vdev->parent) + vdev->dev.parent = vdev->parent; + dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num); + ret = device_register(&vdev->dev); + if (ret < 0) { + printk(KERN_ERR "%s: device_register failed\n", __func__); + goto cleanup; + } + /* Register the release callback that will be called when the last + reference to the device goes away. */ + vdev->dev.release = v4l2_device_release; + + if (nr != -1 && nr != vdev->num && warn_if_nr_in_use) + printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__, + name_base, nr, video_device_node_name(vdev)); + + /* Increase v4l2_device refcount */ + if (vdev->v4l2_dev) + v4l2_device_get(vdev->v4l2_dev); + +#if defined(CONFIG_MEDIA_CONTROLLER) + /* Part 5: Register the entity. */ + if (vdev->v4l2_dev && vdev->v4l2_dev->mdev && + vdev->vfl_type != VFL_TYPE_SUBDEV) { + vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L; + vdev->entity.name = vdev->name; + vdev->entity.info.v4l.major = VIDEO_MAJOR; + vdev->entity.info.v4l.minor = vdev->minor; + ret = media_device_register_entity(vdev->v4l2_dev->mdev, + &vdev->entity); + if (ret < 0) + printk(KERN_WARNING + "%s: media_device_register_entity failed\n", + __func__); + } +#endif + /* Part 6: Activate this minor. The char device can now be used. */ + set_bit(V4L2_FL_REGISTERED, &vdev->flags); + mutex_lock(&videodev_lock); + video_device[vdev->minor] = vdev; + mutex_unlock(&videodev_lock); + + return 0; + +cleanup: + mutex_lock(&videodev_lock); + if (vdev->cdev) + cdev_del(vdev->cdev); + devnode_clear(vdev); + mutex_unlock(&videodev_lock); + /* Mark this video device as never having been registered. */ + vdev->minor = -1; + return ret; +} +EXPORT_SYMBOL(__video_register_device); + +/** + * video_unregister_device - unregister a video4linux device + * @vdev: the device to unregister + * + * This unregisters the passed device. Future open calls will + * be met with errors. + */ +void video_unregister_device(struct video_device *vdev) +{ + /* Check if vdev was ever registered at all */ + if (!vdev || !video_is_registered(vdev)) + return; + + mutex_lock(&videodev_lock); + /* This must be in a critical section to prevent a race with v4l2_open. + * Once this bit has been cleared video_get may never be called again. + */ + clear_bit(V4L2_FL_REGISTERED, &vdev->flags); + mutex_unlock(&videodev_lock); + device_unregister(&vdev->dev); +} +EXPORT_SYMBOL(video_unregister_device); + +/* + * Initialise video for linux + */ +static int __init videodev_init(void) +{ + dev_t dev = MKDEV(VIDEO_MAJOR, 0); + int ret; + + printk(KERN_INFO "Linux video capture interface: v2.00\n"); + ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME); + if (ret < 0) { + printk(KERN_WARNING "videodev: unable to get major %d\n", + VIDEO_MAJOR); + return ret; + } + + ret = class_register(&video_class); + if (ret < 0) { + unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); + printk(KERN_WARNING "video_dev: class_register failed\n"); + return -EIO; + } + + return 0; +} + +static void __exit videodev_exit(void) +{ + dev_t dev = MKDEV(VIDEO_MAJOR, 0); + + class_unregister(&video_class); + unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); +} + +subsys_initcall(videodev_init); +module_exit(videodev_exit) + +MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab <mchehab@infradead.org>"); +MODULE_DESCRIPTION("Device registrar for Video4Linux drivers v2"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR); + + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/v4l2-core/v4l2-device.c b/drivers/media/v4l2-core/v4l2-device.c new file mode 100644 index 000000000000..1f203b85a637 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-device.c @@ -0,0 +1,280 @@ +/* + V4L2 device support. + + Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl> + + 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 + */ + +#include <linux/types.h> +#include <linux/ioctl.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#if defined(CONFIG_SPI) +#include <linux/spi/spi.h> +#endif +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev) +{ + if (v4l2_dev == NULL) + return -EINVAL; + + INIT_LIST_HEAD(&v4l2_dev->subdevs); + spin_lock_init(&v4l2_dev->lock); + mutex_init(&v4l2_dev->ioctl_lock); + v4l2_prio_init(&v4l2_dev->prio); + kref_init(&v4l2_dev->ref); + get_device(dev); + v4l2_dev->dev = dev; + if (dev == NULL) { + /* If dev == NULL, then name must be filled in by the caller */ + WARN_ON(!v4l2_dev->name[0]); + return 0; + } + + /* Set name to driver name + device name if it is empty. */ + if (!v4l2_dev->name[0]) + snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s", + dev->driver->name, dev_name(dev)); + if (!dev_get_drvdata(dev)) + dev_set_drvdata(dev, v4l2_dev); + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_device_register); + +static void v4l2_device_release(struct kref *ref) +{ + struct v4l2_device *v4l2_dev = + container_of(ref, struct v4l2_device, ref); + + if (v4l2_dev->release) + v4l2_dev->release(v4l2_dev); +} + +int v4l2_device_put(struct v4l2_device *v4l2_dev) +{ + return kref_put(&v4l2_dev->ref, v4l2_device_release); +} +EXPORT_SYMBOL_GPL(v4l2_device_put); + +int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename, + atomic_t *instance) +{ + int num = atomic_inc_return(instance) - 1; + int len = strlen(basename); + + if (basename[len - 1] >= '0' && basename[len - 1] <= '9') + snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), + "%s-%d", basename, num); + else + snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), + "%s%d", basename, num); + return num; +} +EXPORT_SYMBOL_GPL(v4l2_device_set_name); + +void v4l2_device_disconnect(struct v4l2_device *v4l2_dev) +{ + if (v4l2_dev->dev == NULL) + return; + + if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev) + dev_set_drvdata(v4l2_dev->dev, NULL); + put_device(v4l2_dev->dev); + v4l2_dev->dev = NULL; +} +EXPORT_SYMBOL_GPL(v4l2_device_disconnect); + +void v4l2_device_unregister(struct v4l2_device *v4l2_dev) +{ + struct v4l2_subdev *sd, *next; + + if (v4l2_dev == NULL) + return; + v4l2_device_disconnect(v4l2_dev); + + /* Unregister subdevs */ + list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) { + v4l2_device_unregister_subdev(sd); +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) + if (sd->flags & V4L2_SUBDEV_FL_IS_I2C) { + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* We need to unregister the i2c client explicitly. + We cannot rely on i2c_del_adapter to always + unregister clients for us, since if the i2c bus + is a platform bus, then it is never deleted. */ + if (client) + i2c_unregister_device(client); + continue; + } +#endif +#if defined(CONFIG_SPI) + if (sd->flags & V4L2_SUBDEV_FL_IS_SPI) { + struct spi_device *spi = v4l2_get_subdevdata(sd); + + if (spi) + spi_unregister_device(spi); + continue; + } +#endif + } +} +EXPORT_SYMBOL_GPL(v4l2_device_unregister); + +int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, + struct v4l2_subdev *sd) +{ +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_entity *entity = &sd->entity; +#endif + int err; + + /* Check for valid input */ + if (v4l2_dev == NULL || sd == NULL || !sd->name[0]) + return -EINVAL; + + /* Warn if we apparently re-register a subdev */ + WARN_ON(sd->v4l2_dev != NULL); + + if (!try_module_get(sd->owner)) + return -ENODEV; + + sd->v4l2_dev = v4l2_dev; + if (sd->internal_ops && sd->internal_ops->registered) { + err = sd->internal_ops->registered(sd); + if (err) { + module_put(sd->owner); + return err; + } + } + + /* This just returns 0 if either of the two args is NULL */ + err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler); + if (err) { + if (sd->internal_ops && sd->internal_ops->unregistered) + sd->internal_ops->unregistered(sd); + module_put(sd->owner); + return err; + } + +#if defined(CONFIG_MEDIA_CONTROLLER) + /* Register the entity. */ + if (v4l2_dev->mdev) { + err = media_device_register_entity(v4l2_dev->mdev, entity); + if (err < 0) { + if (sd->internal_ops && sd->internal_ops->unregistered) + sd->internal_ops->unregistered(sd); + module_put(sd->owner); + return err; + } + } +#endif + + spin_lock(&v4l2_dev->lock); + list_add_tail(&sd->list, &v4l2_dev->subdevs); + spin_unlock(&v4l2_dev->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_device_register_subdev); + +static void v4l2_device_release_subdev_node(struct video_device *vdev) +{ + struct v4l2_subdev *sd = video_get_drvdata(vdev); + sd->devnode = NULL; + kfree(vdev); +} + +int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev) +{ + struct video_device *vdev; + struct v4l2_subdev *sd; + int err; + + /* Register a device node for every subdev marked with the + * V4L2_SUBDEV_FL_HAS_DEVNODE flag. + */ + list_for_each_entry(sd, &v4l2_dev->subdevs, list) { + if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE)) + continue; + + vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); + if (!vdev) { + err = -ENOMEM; + goto clean_up; + } + + video_set_drvdata(vdev, sd); + strlcpy(vdev->name, sd->name, sizeof(vdev->name)); + vdev->v4l2_dev = v4l2_dev; + vdev->fops = &v4l2_subdev_fops; + vdev->release = v4l2_device_release_subdev_node; + vdev->ctrl_handler = sd->ctrl_handler; + err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1, + sd->owner); + if (err < 0) { + kfree(vdev); + goto clean_up; + } +#if defined(CONFIG_MEDIA_CONTROLLER) + sd->entity.info.v4l.major = VIDEO_MAJOR; + sd->entity.info.v4l.minor = vdev->minor; +#endif + sd->devnode = vdev; + } + return 0; + +clean_up: + list_for_each_entry(sd, &v4l2_dev->subdevs, list) { + if (!sd->devnode) + break; + video_unregister_device(sd->devnode); + } + + return err; +} +EXPORT_SYMBOL_GPL(v4l2_device_register_subdev_nodes); + +void v4l2_device_unregister_subdev(struct v4l2_subdev *sd) +{ + struct v4l2_device *v4l2_dev; + + /* return if it isn't registered */ + if (sd == NULL || sd->v4l2_dev == NULL) + return; + + v4l2_dev = sd->v4l2_dev; + + spin_lock(&v4l2_dev->lock); + list_del(&sd->list); + spin_unlock(&v4l2_dev->lock); + + if (sd->internal_ops && sd->internal_ops->unregistered) + sd->internal_ops->unregistered(sd); + sd->v4l2_dev = NULL; + +#if defined(CONFIG_MEDIA_CONTROLLER) + if (v4l2_dev->mdev) + media_device_unregister_entity(&sd->entity); +#endif + video_unregister_device(sd->devnode); + module_put(sd->owner); +} +EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev); diff --git a/drivers/media/v4l2-core/v4l2-event.c b/drivers/media/v4l2-core/v4l2-event.c new file mode 100644 index 000000000000..ef2a33c94045 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-event.c @@ -0,0 +1,313 @@ +/* + * v4l2-event.c + * + * V4L2 events. + * + * Copyright (C) 2009--2010 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> + +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/export.h> + +static unsigned sev_pos(const struct v4l2_subscribed_event *sev, unsigned idx) +{ + idx += sev->first; + return idx >= sev->elems ? idx - sev->elems : idx; +} + +static int __v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event) +{ + struct v4l2_kevent *kev; + unsigned long flags; + + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + + if (list_empty(&fh->available)) { + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + return -ENOENT; + } + + WARN_ON(fh->navailable == 0); + + kev = list_first_entry(&fh->available, struct v4l2_kevent, list); + list_del(&kev->list); + fh->navailable--; + + kev->event.pending = fh->navailable; + *event = kev->event; + kev->sev->first = sev_pos(kev->sev, 1); + kev->sev->in_use--; + + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + + return 0; +} + +int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event, + int nonblocking) +{ + int ret; + + if (nonblocking) + return __v4l2_event_dequeue(fh, event); + + /* Release the vdev lock while waiting */ + if (fh->vdev->lock) + mutex_unlock(fh->vdev->lock); + + do { + ret = wait_event_interruptible(fh->wait, + fh->navailable != 0); + if (ret < 0) + break; + + ret = __v4l2_event_dequeue(fh, event); + } while (ret == -ENOENT); + + if (fh->vdev->lock) + mutex_lock(fh->vdev->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_event_dequeue); + +/* Caller must hold fh->vdev->fh_lock! */ +static struct v4l2_subscribed_event *v4l2_event_subscribed( + struct v4l2_fh *fh, u32 type, u32 id) +{ + struct v4l2_subscribed_event *sev; + + assert_spin_locked(&fh->vdev->fh_lock); + + list_for_each_entry(sev, &fh->subscribed, list) + if (sev->type == type && sev->id == id) + return sev; + + return NULL; +} + +static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev, + const struct timespec *ts) +{ + struct v4l2_subscribed_event *sev; + struct v4l2_kevent *kev; + bool copy_payload = true; + + /* Are we subscribed? */ + sev = v4l2_event_subscribed(fh, ev->type, ev->id); + if (sev == NULL) + return; + + /* + * If the event has been added to the fh->subscribed list, but its + * add op has not completed yet elems will be 0, treat this as + * not being subscribed. + */ + if (!sev->elems) + return; + + /* Increase event sequence number on fh. */ + fh->sequence++; + + /* Do we have any free events? */ + if (sev->in_use == sev->elems) { + /* no, remove the oldest one */ + kev = sev->events + sev_pos(sev, 0); + list_del(&kev->list); + sev->in_use--; + sev->first = sev_pos(sev, 1); + fh->navailable--; + if (sev->elems == 1) { + if (sev->ops && sev->ops->replace) { + sev->ops->replace(&kev->event, ev); + copy_payload = false; + } + } else if (sev->ops && sev->ops->merge) { + struct v4l2_kevent *second_oldest = + sev->events + sev_pos(sev, 0); + sev->ops->merge(&kev->event, &second_oldest->event); + } + } + + /* Take one and fill it. */ + kev = sev->events + sev_pos(sev, sev->in_use); + kev->event.type = ev->type; + if (copy_payload) + kev->event.u = ev->u; + kev->event.id = ev->id; + kev->event.timestamp = *ts; + kev->event.sequence = fh->sequence; + sev->in_use++; + list_add_tail(&kev->list, &fh->available); + + fh->navailable++; + + wake_up_all(&fh->wait); +} + +void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) +{ + struct v4l2_fh *fh; + unsigned long flags; + struct timespec timestamp; + + ktime_get_ts(×tamp); + + spin_lock_irqsave(&vdev->fh_lock, flags); + + list_for_each_entry(fh, &vdev->fh_list, list) + __v4l2_event_queue_fh(fh, ev, ×tamp); + + spin_unlock_irqrestore(&vdev->fh_lock, flags); +} +EXPORT_SYMBOL_GPL(v4l2_event_queue); + +void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev) +{ + unsigned long flags; + struct timespec timestamp; + + ktime_get_ts(×tamp); + + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + __v4l2_event_queue_fh(fh, ev, ×tamp); + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); +} +EXPORT_SYMBOL_GPL(v4l2_event_queue_fh); + +int v4l2_event_pending(struct v4l2_fh *fh) +{ + return fh->navailable; +} +EXPORT_SYMBOL_GPL(v4l2_event_pending); + +int v4l2_event_subscribe(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub, unsigned elems, + const struct v4l2_subscribed_event_ops *ops) +{ + struct v4l2_subscribed_event *sev, *found_ev; + unsigned long flags; + unsigned i; + + if (sub->type == V4L2_EVENT_ALL) + return -EINVAL; + + if (elems < 1) + elems = 1; + + sev = kzalloc(sizeof(*sev) + sizeof(struct v4l2_kevent) * elems, GFP_KERNEL); + if (!sev) + return -ENOMEM; + for (i = 0; i < elems; i++) + sev->events[i].sev = sev; + sev->type = sub->type; + sev->id = sub->id; + sev->flags = sub->flags; + sev->fh = fh; + sev->ops = ops; + + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + found_ev = v4l2_event_subscribed(fh, sub->type, sub->id); + if (!found_ev) + list_add(&sev->list, &fh->subscribed); + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + + if (found_ev) { + kfree(sev); + return 0; /* Already listening */ + } + + if (sev->ops && sev->ops->add) { + int ret = sev->ops->add(sev, elems); + if (ret) { + sev->ops = NULL; + v4l2_event_unsubscribe(fh, sub); + return ret; + } + } + + /* Mark as ready for use */ + sev->elems = elems; + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_event_subscribe); + +void v4l2_event_unsubscribe_all(struct v4l2_fh *fh) +{ + struct v4l2_event_subscription sub; + struct v4l2_subscribed_event *sev; + unsigned long flags; + + do { + sev = NULL; + + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + if (!list_empty(&fh->subscribed)) { + sev = list_first_entry(&fh->subscribed, + struct v4l2_subscribed_event, list); + sub.type = sev->type; + sub.id = sev->id; + } + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + if (sev) + v4l2_event_unsubscribe(fh, &sub); + } while (sev); +} +EXPORT_SYMBOL_GPL(v4l2_event_unsubscribe_all); + +int v4l2_event_unsubscribe(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + struct v4l2_subscribed_event *sev; + unsigned long flags; + int i; + + if (sub->type == V4L2_EVENT_ALL) { + v4l2_event_unsubscribe_all(fh); + return 0; + } + + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + + sev = v4l2_event_subscribed(fh, sub->type, sub->id); + if (sev != NULL) { + /* Remove any pending events for this subscription */ + for (i = 0; i < sev->in_use; i++) { + list_del(&sev->events[sev_pos(sev, i)].list); + fh->navailable--; + } + list_del(&sev->list); + } + + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + + if (sev && sev->ops && sev->ops->del) + sev->ops->del(sev); + + kfree(sev); + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_event_unsubscribe); diff --git a/drivers/media/v4l2-core/v4l2-fh.c b/drivers/media/v4l2-core/v4l2-fh.c new file mode 100644 index 000000000000..9e3fc040ea20 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-fh.c @@ -0,0 +1,120 @@ +/* + * v4l2-fh.c + * + * V4L2 file handles. + * + * Copyright (C) 2009--2010 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@maxwell.research.nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> + +void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) +{ + fh->vdev = vdev; + /* Inherit from video_device. May be overridden by the driver. */ + fh->ctrl_handler = vdev->ctrl_handler; + INIT_LIST_HEAD(&fh->list); + set_bit(V4L2_FL_USES_V4L2_FH, &fh->vdev->flags); + fh->prio = V4L2_PRIORITY_UNSET; + init_waitqueue_head(&fh->wait); + INIT_LIST_HEAD(&fh->available); + INIT_LIST_HEAD(&fh->subscribed); + fh->sequence = -1; +} +EXPORT_SYMBOL_GPL(v4l2_fh_init); + +void v4l2_fh_add(struct v4l2_fh *fh) +{ + unsigned long flags; + + if (test_bit(V4L2_FL_USE_FH_PRIO, &fh->vdev->flags)) + v4l2_prio_open(fh->vdev->prio, &fh->prio); + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + list_add(&fh->list, &fh->vdev->fh_list); + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); +} +EXPORT_SYMBOL_GPL(v4l2_fh_add); + +int v4l2_fh_open(struct file *filp) +{ + struct video_device *vdev = video_devdata(filp); + struct v4l2_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL); + + filp->private_data = fh; + if (fh == NULL) + return -ENOMEM; + v4l2_fh_init(fh, vdev); + v4l2_fh_add(fh); + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_fh_open); + +void v4l2_fh_del(struct v4l2_fh *fh) +{ + unsigned long flags; + + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + list_del_init(&fh->list); + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + if (test_bit(V4L2_FL_USE_FH_PRIO, &fh->vdev->flags)) + v4l2_prio_close(fh->vdev->prio, fh->prio); +} +EXPORT_SYMBOL_GPL(v4l2_fh_del); + +void v4l2_fh_exit(struct v4l2_fh *fh) +{ + if (fh->vdev == NULL) + return; + v4l2_event_unsubscribe_all(fh); + fh->vdev = NULL; +} +EXPORT_SYMBOL_GPL(v4l2_fh_exit); + +int v4l2_fh_release(struct file *filp) +{ + struct v4l2_fh *fh = filp->private_data; + + if (fh) { + v4l2_fh_del(fh); + v4l2_fh_exit(fh); + kfree(fh); + } + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_fh_release); + +int v4l2_fh_is_singular(struct v4l2_fh *fh) +{ + unsigned long flags; + int is_singular; + + if (fh == NULL || fh->vdev == NULL) + return 0; + spin_lock_irqsave(&fh->vdev->fh_lock, flags); + is_singular = list_is_singular(&fh->list); + spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); + return is_singular; +} +EXPORT_SYMBOL_GPL(v4l2_fh_is_singular); diff --git a/drivers/media/v4l2-core/v4l2-int-device.c b/drivers/media/v4l2-core/v4l2-int-device.c new file mode 100644 index 000000000000..f4473494af7a --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-int-device.c @@ -0,0 +1,164 @@ +/* + * drivers/media/video/v4l2-int-device.c + * + * V4L2 internal ioctl interface. + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/sort.h> +#include <linux/string.h> +#include <linux/module.h> + +#include <media/v4l2-int-device.h> + +static DEFINE_MUTEX(mutex); +static LIST_HEAD(int_list); + +void v4l2_int_device_try_attach_all(void) +{ + struct v4l2_int_device *m, *s; + + list_for_each_entry(m, &int_list, head) { + if (m->type != v4l2_int_type_master) + continue; + + list_for_each_entry(s, &int_list, head) { + if (s->type != v4l2_int_type_slave) + continue; + + /* Slave is connected? */ + if (s->u.slave->master) + continue; + + /* Slave wants to attach to master? */ + if (s->u.slave->attach_to[0] != 0 + && strncmp(m->name, s->u.slave->attach_to, + V4L2NAMESIZE)) + continue; + + if (!try_module_get(m->module)) + continue; + + s->u.slave->master = m; + if (m->u.master->attach(s)) { + s->u.slave->master = NULL; + module_put(m->module); + continue; + } + } + } +} +EXPORT_SYMBOL_GPL(v4l2_int_device_try_attach_all); + +static int ioctl_sort_cmp(const void *a, const void *b) +{ + const struct v4l2_int_ioctl_desc *d1 = a, *d2 = b; + + if (d1->num > d2->num) + return 1; + + if (d1->num < d2->num) + return -1; + + return 0; +} + +int v4l2_int_device_register(struct v4l2_int_device *d) +{ + if (d->type == v4l2_int_type_slave) + sort(d->u.slave->ioctls, d->u.slave->num_ioctls, + sizeof(struct v4l2_int_ioctl_desc), + &ioctl_sort_cmp, NULL); + mutex_lock(&mutex); + list_add(&d->head, &int_list); + v4l2_int_device_try_attach_all(); + mutex_unlock(&mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_int_device_register); + +void v4l2_int_device_unregister(struct v4l2_int_device *d) +{ + mutex_lock(&mutex); + list_del(&d->head); + if (d->type == v4l2_int_type_slave + && d->u.slave->master != NULL) { + d->u.slave->master->u.master->detach(d); + module_put(d->u.slave->master->module); + d->u.slave->master = NULL; + } + mutex_unlock(&mutex); +} +EXPORT_SYMBOL_GPL(v4l2_int_device_unregister); + +/* Adapted from search_extable in extable.c. */ +static v4l2_int_ioctl_func *find_ioctl(struct v4l2_int_slave *slave, int cmd, + v4l2_int_ioctl_func *no_such_ioctl) +{ + const struct v4l2_int_ioctl_desc *first = slave->ioctls; + const struct v4l2_int_ioctl_desc *last = + first + slave->num_ioctls - 1; + + while (first <= last) { + const struct v4l2_int_ioctl_desc *mid; + + mid = (last - first) / 2 + first; + + if (mid->num < cmd) + first = mid + 1; + else if (mid->num > cmd) + last = mid - 1; + else + return mid->func; + } + + return no_such_ioctl; +} + +static int no_such_ioctl_0(struct v4l2_int_device *d) +{ + return -ENOIOCTLCMD; +} + +int v4l2_int_ioctl_0(struct v4l2_int_device *d, int cmd) +{ + return ((v4l2_int_ioctl_func_0 *) + find_ioctl(d->u.slave, cmd, + (v4l2_int_ioctl_func *)no_such_ioctl_0))(d); +} +EXPORT_SYMBOL_GPL(v4l2_int_ioctl_0); + +static int no_such_ioctl_1(struct v4l2_int_device *d, void *arg) +{ + return -ENOIOCTLCMD; +} + +int v4l2_int_ioctl_1(struct v4l2_int_device *d, int cmd, void *arg) +{ + return ((v4l2_int_ioctl_func_1 *) + find_ioctl(d->u.slave, cmd, + (v4l2_int_ioctl_func *)no_such_ioctl_1))(d, arg); +} +EXPORT_SYMBOL_GPL(v4l2_int_ioctl_1); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c new file mode 100644 index 000000000000..c3b7b5f59b32 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -0,0 +1,2324 @@ +/* + * Video capture interface for Linux version 2 + * + * A generic framework to process V4L2 ioctl commands. + * + * 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. + * + * Authors: Alan Cox, <alan@lxorguk.ukuu.org.uk> (version 1) + * Mauro Carvalho Chehab <mchehab@infradead.org> (version 2) + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/version.h> + +#include <linux/videodev2.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> +#include <media/v4l2-device.h> +#include <media/v4l2-chip-ident.h> +#include <media/videobuf2-core.h> + +/* Zero out the end of the struct pointed to by p. Everything after, but + * not including, the specified field is cleared. */ +#define CLEAR_AFTER_FIELD(p, field) \ + memset((u8 *)(p) + offsetof(typeof(*(p)), field) + sizeof((p)->field), \ + 0, sizeof(*(p)) - offsetof(typeof(*(p)), field) - sizeof((p)->field)) + +struct std_descr { + v4l2_std_id std; + const char *descr; +}; + +static const struct std_descr standards[] = { + { V4L2_STD_NTSC, "NTSC" }, + { V4L2_STD_NTSC_M, "NTSC-M" }, + { V4L2_STD_NTSC_M_JP, "NTSC-M-JP" }, + { V4L2_STD_NTSC_M_KR, "NTSC-M-KR" }, + { V4L2_STD_NTSC_443, "NTSC-443" }, + { V4L2_STD_PAL, "PAL" }, + { V4L2_STD_PAL_BG, "PAL-BG" }, + { V4L2_STD_PAL_B, "PAL-B" }, + { V4L2_STD_PAL_B1, "PAL-B1" }, + { V4L2_STD_PAL_G, "PAL-G" }, + { V4L2_STD_PAL_H, "PAL-H" }, + { V4L2_STD_PAL_I, "PAL-I" }, + { V4L2_STD_PAL_DK, "PAL-DK" }, + { V4L2_STD_PAL_D, "PAL-D" }, + { V4L2_STD_PAL_D1, "PAL-D1" }, + { V4L2_STD_PAL_K, "PAL-K" }, + { V4L2_STD_PAL_M, "PAL-M" }, + { V4L2_STD_PAL_N, "PAL-N" }, + { V4L2_STD_PAL_Nc, "PAL-Nc" }, + { V4L2_STD_PAL_60, "PAL-60" }, + { V4L2_STD_SECAM, "SECAM" }, + { V4L2_STD_SECAM_B, "SECAM-B" }, + { V4L2_STD_SECAM_G, "SECAM-G" }, + { V4L2_STD_SECAM_H, "SECAM-H" }, + { V4L2_STD_SECAM_DK, "SECAM-DK" }, + { V4L2_STD_SECAM_D, "SECAM-D" }, + { V4L2_STD_SECAM_K, "SECAM-K" }, + { V4L2_STD_SECAM_K1, "SECAM-K1" }, + { V4L2_STD_SECAM_L, "SECAM-L" }, + { V4L2_STD_SECAM_LC, "SECAM-Lc" }, + { 0, "Unknown" } +}; + +/* video4linux standard ID conversion to standard name + */ +const char *v4l2_norm_to_name(v4l2_std_id id) +{ + u32 myid = id; + int i; + + /* HACK: ppc32 architecture doesn't have __ucmpdi2 function to handle + 64 bit comparations. So, on that architecture, with some gcc + variants, compilation fails. Currently, the max value is 30bit wide. + */ + BUG_ON(myid != id); + + for (i = 0; standards[i].std; i++) + if (myid == standards[i].std) + break; + return standards[i].descr; +} +EXPORT_SYMBOL(v4l2_norm_to_name); + +/* Returns frame period for the given standard */ +void v4l2_video_std_frame_period(int id, struct v4l2_fract *frameperiod) +{ + if (id & V4L2_STD_525_60) { + frameperiod->numerator = 1001; + frameperiod->denominator = 30000; + } else { + frameperiod->numerator = 1; + frameperiod->denominator = 25; + } +} +EXPORT_SYMBOL(v4l2_video_std_frame_period); + +/* Fill in the fields of a v4l2_standard structure according to the + 'id' and 'transmission' parameters. Returns negative on error. */ +int v4l2_video_std_construct(struct v4l2_standard *vs, + int id, const char *name) +{ + vs->id = id; + v4l2_video_std_frame_period(id, &vs->frameperiod); + vs->framelines = (id & V4L2_STD_525_60) ? 525 : 625; + strlcpy(vs->name, name, sizeof(vs->name)); + return 0; +} +EXPORT_SYMBOL(v4l2_video_std_construct); + +/* ----------------------------------------------------------------- */ +/* some arrays for pretty-printing debug messages of enum types */ + +const char *v4l2_field_names[] = { + [V4L2_FIELD_ANY] = "any", + [V4L2_FIELD_NONE] = "none", + [V4L2_FIELD_TOP] = "top", + [V4L2_FIELD_BOTTOM] = "bottom", + [V4L2_FIELD_INTERLACED] = "interlaced", + [V4L2_FIELD_SEQ_TB] = "seq-tb", + [V4L2_FIELD_SEQ_BT] = "seq-bt", + [V4L2_FIELD_ALTERNATE] = "alternate", + [V4L2_FIELD_INTERLACED_TB] = "interlaced-tb", + [V4L2_FIELD_INTERLACED_BT] = "interlaced-bt", +}; +EXPORT_SYMBOL(v4l2_field_names); + +const char *v4l2_type_names[] = { + [V4L2_BUF_TYPE_VIDEO_CAPTURE] = "vid-cap", + [V4L2_BUF_TYPE_VIDEO_OVERLAY] = "vid-overlay", + [V4L2_BUF_TYPE_VIDEO_OUTPUT] = "vid-out", + [V4L2_BUF_TYPE_VBI_CAPTURE] = "vbi-cap", + [V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out", + [V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-cap", + [V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "sliced-vbi-out", + [V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY] = "vid-out-overlay", + [V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE] = "vid-cap-mplane", + [V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE] = "vid-out-mplane", +}; +EXPORT_SYMBOL(v4l2_type_names); + +static const char *v4l2_memory_names[] = { + [V4L2_MEMORY_MMAP] = "mmap", + [V4L2_MEMORY_USERPTR] = "userptr", + [V4L2_MEMORY_OVERLAY] = "overlay", +}; + +#define prt_names(a, arr) ((((a) >= 0) && ((a) < ARRAY_SIZE(arr))) ? \ + arr[a] : "unknown") + +/* ------------------------------------------------------------------ */ +/* debug help functions */ + +static void v4l_print_querycap(const void *arg, bool write_only) +{ + const struct v4l2_capability *p = arg; + + pr_cont("driver=%s, card=%s, bus=%s, version=0x%08x, " + "capabilities=0x%08x, device_caps=0x%08x\n", + p->driver, p->card, p->bus_info, + p->version, p->capabilities, p->device_caps); +} + +static void v4l_print_enuminput(const void *arg, bool write_only) +{ + const struct v4l2_input *p = arg; + + pr_cont("index=%u, name=%s, type=%u, audioset=0x%x, tuner=%u, " + "std=0x%08Lx, status=0x%x, capabilities=0x%x\n", + p->index, p->name, p->type, p->audioset, p->tuner, + (unsigned long long)p->std, p->status, p->capabilities); +} + +static void v4l_print_enumoutput(const void *arg, bool write_only) +{ + const struct v4l2_output *p = arg; + + pr_cont("index=%u, name=%s, type=%u, audioset=0x%x, " + "modulator=%u, std=0x%08Lx, capabilities=0x%x\n", + p->index, p->name, p->type, p->audioset, p->modulator, + (unsigned long long)p->std, p->capabilities); +} + +static void v4l_print_audio(const void *arg, bool write_only) +{ + const struct v4l2_audio *p = arg; + + if (write_only) + pr_cont("index=%u, mode=0x%x\n", p->index, p->mode); + else + pr_cont("index=%u, name=%s, capability=0x%x, mode=0x%x\n", + p->index, p->name, p->capability, p->mode); +} + +static void v4l_print_audioout(const void *arg, bool write_only) +{ + const struct v4l2_audioout *p = arg; + + if (write_only) + pr_cont("index=%u\n", p->index); + else + pr_cont("index=%u, name=%s, capability=0x%x, mode=0x%x\n", + p->index, p->name, p->capability, p->mode); +} + +static void v4l_print_fmtdesc(const void *arg, bool write_only) +{ + const struct v4l2_fmtdesc *p = arg; + + pr_cont("index=%u, type=%s, flags=0x%x, pixelformat=%c%c%c%c, description='%s'\n", + p->index, prt_names(p->type, v4l2_type_names), + p->flags, (p->pixelformat & 0xff), + (p->pixelformat >> 8) & 0xff, + (p->pixelformat >> 16) & 0xff, + (p->pixelformat >> 24) & 0xff, + p->description); +} + +static void v4l_print_format(const void *arg, bool write_only) +{ + const struct v4l2_format *p = arg; + const struct v4l2_pix_format *pix; + const struct v4l2_pix_format_mplane *mp; + const struct v4l2_vbi_format *vbi; + const struct v4l2_sliced_vbi_format *sliced; + const struct v4l2_window *win; + const struct v4l2_clip *clip; + unsigned i; + + pr_cont("type=%s", prt_names(p->type, v4l2_type_names)); + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + pix = &p->fmt.pix; + pr_cont(", width=%u, height=%u, " + "pixelformat=%c%c%c%c, field=%s, " + "bytesperline=%u sizeimage=%u, colorspace=%d\n", + pix->width, pix->height, + (pix->pixelformat & 0xff), + (pix->pixelformat >> 8) & 0xff, + (pix->pixelformat >> 16) & 0xff, + (pix->pixelformat >> 24) & 0xff, + prt_names(pix->field, v4l2_field_names), + pix->bytesperline, pix->sizeimage, + pix->colorspace); + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + mp = &p->fmt.pix_mp; + pr_cont(", width=%u, height=%u, " + "format=%c%c%c%c, field=%s, " + "colorspace=%d, num_planes=%u\n", + mp->width, mp->height, + (mp->pixelformat & 0xff), + (mp->pixelformat >> 8) & 0xff, + (mp->pixelformat >> 16) & 0xff, + (mp->pixelformat >> 24) & 0xff, + prt_names(mp->field, v4l2_field_names), + mp->colorspace, mp->num_planes); + for (i = 0; i < mp->num_planes; i++) + printk(KERN_DEBUG "plane %u: bytesperline=%u sizeimage=%u\n", i, + mp->plane_fmt[i].bytesperline, + mp->plane_fmt[i].sizeimage); + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + win = &p->fmt.win; + pr_cont(", wxh=%dx%d, x,y=%d,%d, field=%s, " + "chromakey=0x%08x, bitmap=%p, " + "global_alpha=0x%02x\n", + win->w.width, win->w.height, + win->w.left, win->w.top, + prt_names(win->field, v4l2_field_names), + win->chromakey, win->bitmap, win->global_alpha); + clip = win->clips; + for (i = 0; i < win->clipcount; i++) { + printk(KERN_DEBUG "clip %u: wxh=%dx%d, x,y=%d,%d\n", + i, clip->c.width, clip->c.height, + clip->c.left, clip->c.top); + clip = clip->next; + } + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + vbi = &p->fmt.vbi; + pr_cont(", sampling_rate=%u, offset=%u, samples_per_line=%u, " + "sample_format=%c%c%c%c, start=%u,%u, count=%u,%u\n", + vbi->sampling_rate, vbi->offset, + vbi->samples_per_line, + (vbi->sample_format & 0xff), + (vbi->sample_format >> 8) & 0xff, + (vbi->sample_format >> 16) & 0xff, + (vbi->sample_format >> 24) & 0xff, + vbi->start[0], vbi->start[1], + vbi->count[0], vbi->count[1]); + break; + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + sliced = &p->fmt.sliced; + pr_cont(", service_set=0x%08x, io_size=%d\n", + sliced->service_set, sliced->io_size); + for (i = 0; i < 24; i++) + printk(KERN_DEBUG "line[%02u]=0x%04x, 0x%04x\n", i, + sliced->service_lines[0][i], + sliced->service_lines[1][i]); + break; + case V4L2_BUF_TYPE_PRIVATE: + pr_cont("\n"); + break; + } +} + +static void v4l_print_framebuffer(const void *arg, bool write_only) +{ + const struct v4l2_framebuffer *p = arg; + + pr_cont("capability=0x%x, flags=0x%x, base=0x%p, width=%u, " + "height=%u, pixelformat=%c%c%c%c, " + "bytesperline=%u sizeimage=%u, colorspace=%d\n", + p->capability, p->flags, p->base, + p->fmt.width, p->fmt.height, + (p->fmt.pixelformat & 0xff), + (p->fmt.pixelformat >> 8) & 0xff, + (p->fmt.pixelformat >> 16) & 0xff, + (p->fmt.pixelformat >> 24) & 0xff, + p->fmt.bytesperline, p->fmt.sizeimage, + p->fmt.colorspace); +} + +static void v4l_print_buftype(const void *arg, bool write_only) +{ + pr_cont("type=%s\n", prt_names(*(u32 *)arg, v4l2_type_names)); +} + +static void v4l_print_modulator(const void *arg, bool write_only) +{ + const struct v4l2_modulator *p = arg; + + if (write_only) + pr_cont("index=%u, txsubchans=0x%x", p->index, p->txsubchans); + else + pr_cont("index=%u, name=%s, capability=0x%x, " + "rangelow=%u, rangehigh=%u, txsubchans=0x%x\n", + p->index, p->name, p->capability, + p->rangelow, p->rangehigh, p->txsubchans); +} + +static void v4l_print_tuner(const void *arg, bool write_only) +{ + const struct v4l2_tuner *p = arg; + + if (write_only) + pr_cont("index=%u, audmode=%u\n", p->index, p->audmode); + else + pr_cont("index=%u, name=%s, type=%u, capability=0x%x, " + "rangelow=%u, rangehigh=%u, signal=%u, afc=%d, " + "rxsubchans=0x%x, audmode=%u\n", + p->index, p->name, p->type, + p->capability, p->rangelow, + p->rangehigh, p->signal, p->afc, + p->rxsubchans, p->audmode); +} + +static void v4l_print_frequency(const void *arg, bool write_only) +{ + const struct v4l2_frequency *p = arg; + + pr_cont("tuner=%u, type=%u, frequency=%u\n", + p->tuner, p->type, p->frequency); +} + +static void v4l_print_standard(const void *arg, bool write_only) +{ + const struct v4l2_standard *p = arg; + + pr_cont("index=%u, id=0x%Lx, name=%s, fps=%u/%u, " + "framelines=%u\n", p->index, + (unsigned long long)p->id, p->name, + p->frameperiod.numerator, + p->frameperiod.denominator, + p->framelines); +} + +static void v4l_print_std(const void *arg, bool write_only) +{ + pr_cont("std=0x%08Lx\n", *(const long long unsigned *)arg); +} + +static void v4l_print_hw_freq_seek(const void *arg, bool write_only) +{ + const struct v4l2_hw_freq_seek *p = arg; + + pr_cont("tuner=%u, type=%u, seek_upward=%u, wrap_around=%u, spacing=%u\n", + p->tuner, p->type, p->seek_upward, p->wrap_around, p->spacing); +} + +static void v4l_print_requestbuffers(const void *arg, bool write_only) +{ + const struct v4l2_requestbuffers *p = arg; + + pr_cont("count=%d, type=%s, memory=%s\n", + p->count, + prt_names(p->type, v4l2_type_names), + prt_names(p->memory, v4l2_memory_names)); +} + +static void v4l_print_buffer(const void *arg, bool write_only) +{ + const struct v4l2_buffer *p = arg; + const struct v4l2_timecode *tc = &p->timecode; + const struct v4l2_plane *plane; + int i; + + pr_cont("%02ld:%02d:%02d.%08ld index=%d, type=%s, " + "flags=0x%08x, field=%s, sequence=%d, memory=%s", + p->timestamp.tv_sec / 3600, + (int)(p->timestamp.tv_sec / 60) % 60, + (int)(p->timestamp.tv_sec % 60), + (long)p->timestamp.tv_usec, + p->index, + prt_names(p->type, v4l2_type_names), + p->flags, prt_names(p->field, v4l2_field_names), + p->sequence, prt_names(p->memory, v4l2_memory_names)); + + if (V4L2_TYPE_IS_MULTIPLANAR(p->type) && p->m.planes) { + pr_cont("\n"); + for (i = 0; i < p->length; ++i) { + plane = &p->m.planes[i]; + printk(KERN_DEBUG + "plane %d: bytesused=%d, data_offset=0x%08x " + "offset/userptr=0x%lx, length=%d\n", + i, plane->bytesused, plane->data_offset, + plane->m.userptr, plane->length); + } + } else { + pr_cont("bytesused=%d, offset/userptr=0x%lx, length=%d\n", + p->bytesused, p->m.userptr, p->length); + } + + printk(KERN_DEBUG "timecode=%02d:%02d:%02d type=%d, " + "flags=0x%08x, frames=%d, userbits=0x%08x\n", + tc->hours, tc->minutes, tc->seconds, + tc->type, tc->flags, tc->frames, *(__u32 *)tc->userbits); +} + +static void v4l_print_create_buffers(const void *arg, bool write_only) +{ + const struct v4l2_create_buffers *p = arg; + + pr_cont("index=%d, count=%d, memory=%s, ", + p->index, p->count, + prt_names(p->memory, v4l2_memory_names)); + v4l_print_format(&p->format, write_only); +} + +static void v4l_print_streamparm(const void *arg, bool write_only) +{ + const struct v4l2_streamparm *p = arg; + + pr_cont("type=%s", prt_names(p->type, v4l2_type_names)); + + if (p->type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + p->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + const struct v4l2_captureparm *c = &p->parm.capture; + + pr_cont(", capability=0x%x, capturemode=0x%x, timeperframe=%d/%d, " + "extendedmode=%d, readbuffers=%d\n", + c->capability, c->capturemode, + c->timeperframe.numerator, c->timeperframe.denominator, + c->extendedmode, c->readbuffers); + } else if (p->type == V4L2_BUF_TYPE_VIDEO_OUTPUT || + p->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + const struct v4l2_outputparm *c = &p->parm.output; + + pr_cont(", capability=0x%x, outputmode=0x%x, timeperframe=%d/%d, " + "extendedmode=%d, writebuffers=%d\n", + c->capability, c->outputmode, + c->timeperframe.numerator, c->timeperframe.denominator, + c->extendedmode, c->writebuffers); + } +} + +static void v4l_print_queryctrl(const void *arg, bool write_only) +{ + const struct v4l2_queryctrl *p = arg; + + pr_cont("id=0x%x, type=%d, name=%s, min/max=%d/%d, " + "step=%d, default=%d, flags=0x%08x\n", + p->id, p->type, p->name, + p->minimum, p->maximum, + p->step, p->default_value, p->flags); +} + +static void v4l_print_querymenu(const void *arg, bool write_only) +{ + const struct v4l2_querymenu *p = arg; + + pr_cont("id=0x%x, index=%d\n", p->id, p->index); +} + +static void v4l_print_control(const void *arg, bool write_only) +{ + const struct v4l2_control *p = arg; + + pr_cont("id=0x%x, value=%d\n", p->id, p->value); +} + +static void v4l_print_ext_controls(const void *arg, bool write_only) +{ + const struct v4l2_ext_controls *p = arg; + int i; + + pr_cont("class=0x%x, count=%d, error_idx=%d", + p->ctrl_class, p->count, p->error_idx); + for (i = 0; i < p->count; i++) { + if (p->controls[i].size) + pr_cont(", id/val=0x%x/0x%x", + p->controls[i].id, p->controls[i].value); + else + pr_cont(", id/size=0x%x/%u", + p->controls[i].id, p->controls[i].size); + } + pr_cont("\n"); +} + +static void v4l_print_cropcap(const void *arg, bool write_only) +{ + const struct v4l2_cropcap *p = arg; + + pr_cont("type=%s, bounds wxh=%dx%d, x,y=%d,%d, " + "defrect wxh=%dx%d, x,y=%d,%d\n, " + "pixelaspect %d/%d\n", + prt_names(p->type, v4l2_type_names), + p->bounds.width, p->bounds.height, + p->bounds.left, p->bounds.top, + p->defrect.width, p->defrect.height, + p->defrect.left, p->defrect.top, + p->pixelaspect.numerator, p->pixelaspect.denominator); +} + +static void v4l_print_crop(const void *arg, bool write_only) +{ + const struct v4l2_crop *p = arg; + + pr_cont("type=%s, wxh=%dx%d, x,y=%d,%d\n", + prt_names(p->type, v4l2_type_names), + p->c.width, p->c.height, + p->c.left, p->c.top); +} + +static void v4l_print_selection(const void *arg, bool write_only) +{ + const struct v4l2_selection *p = arg; + + pr_cont("type=%s, target=%d, flags=0x%x, wxh=%dx%d, x,y=%d,%d\n", + prt_names(p->type, v4l2_type_names), + p->target, p->flags, + p->r.width, p->r.height, p->r.left, p->r.top); +} + +static void v4l_print_jpegcompression(const void *arg, bool write_only) +{ + const struct v4l2_jpegcompression *p = arg; + + pr_cont("quality=%d, APPn=%d, APP_len=%d, " + "COM_len=%d, jpeg_markers=0x%x\n", + p->quality, p->APPn, p->APP_len, + p->COM_len, p->jpeg_markers); +} + +static void v4l_print_enc_idx(const void *arg, bool write_only) +{ + const struct v4l2_enc_idx *p = arg; + + pr_cont("entries=%d, entries_cap=%d\n", + p->entries, p->entries_cap); +} + +static void v4l_print_encoder_cmd(const void *arg, bool write_only) +{ + const struct v4l2_encoder_cmd *p = arg; + + pr_cont("cmd=%d, flags=0x%x\n", + p->cmd, p->flags); +} + +static void v4l_print_decoder_cmd(const void *arg, bool write_only) +{ + const struct v4l2_decoder_cmd *p = arg; + + pr_cont("cmd=%d, flags=0x%x\n", p->cmd, p->flags); + + if (p->cmd == V4L2_DEC_CMD_START) + pr_info("speed=%d, format=%u\n", + p->start.speed, p->start.format); + else if (p->cmd == V4L2_DEC_CMD_STOP) + pr_info("pts=%llu\n", p->stop.pts); +} + +static void v4l_print_dbg_chip_ident(const void *arg, bool write_only) +{ + const struct v4l2_dbg_chip_ident *p = arg; + + pr_cont("type=%u, ", p->match.type); + if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER) + pr_cont("name=%s, ", p->match.name); + else + pr_cont("addr=%u, ", p->match.addr); + pr_cont("chip_ident=%u, revision=0x%x\n", + p->ident, p->revision); +} + +static void v4l_print_dbg_register(const void *arg, bool write_only) +{ + const struct v4l2_dbg_register *p = arg; + + pr_cont("type=%u, ", p->match.type); + if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER) + pr_cont("name=%s, ", p->match.name); + else + pr_cont("addr=%u, ", p->match.addr); + pr_cont("reg=0x%llx, val=0x%llx\n", + p->reg, p->val); +} + +static void v4l_print_dv_enum_presets(const void *arg, bool write_only) +{ + const struct v4l2_dv_enum_preset *p = arg; + + pr_cont("index=%u, preset=%u, name=%s, width=%u, height=%u\n", + p->index, p->preset, p->name, p->width, p->height); +} + +static void v4l_print_dv_preset(const void *arg, bool write_only) +{ + const struct v4l2_dv_preset *p = arg; + + pr_cont("preset=%u\n", p->preset); +} + +static void v4l_print_dv_timings(const void *arg, bool write_only) +{ + const struct v4l2_dv_timings *p = arg; + + switch (p->type) { + case V4L2_DV_BT_656_1120: + pr_cont("type=bt-656/1120, interlaced=%u, " + "pixelclock=%llu, " + "width=%u, height=%u, polarities=0x%x, " + "hfrontporch=%u, hsync=%u, " + "hbackporch=%u, vfrontporch=%u, " + "vsync=%u, vbackporch=%u, " + "il_vfrontporch=%u, il_vsync=%u, " + "il_vbackporch=%u, standards=0x%x, flags=0x%x\n", + p->bt.interlaced, p->bt.pixelclock, + p->bt.width, p->bt.height, + p->bt.polarities, p->bt.hfrontporch, + p->bt.hsync, p->bt.hbackporch, + p->bt.vfrontporch, p->bt.vsync, + p->bt.vbackporch, p->bt.il_vfrontporch, + p->bt.il_vsync, p->bt.il_vbackporch, + p->bt.standards, p->bt.flags); + break; + default: + pr_cont("type=%d\n", p->type); + break; + } +} + +static void v4l_print_enum_dv_timings(const void *arg, bool write_only) +{ + const struct v4l2_enum_dv_timings *p = arg; + + pr_cont("index=%u, ", p->index); + v4l_print_dv_timings(&p->timings, write_only); +} + +static void v4l_print_dv_timings_cap(const void *arg, bool write_only) +{ + const struct v4l2_dv_timings_cap *p = arg; + + switch (p->type) { + case V4L2_DV_BT_656_1120: + pr_cont("type=bt-656/1120, width=%u-%u, height=%u-%u, " + "pixelclock=%llu-%llu, standards=0x%x, capabilities=0x%x\n", + p->bt.min_width, p->bt.max_width, + p->bt.min_height, p->bt.max_height, + p->bt.min_pixelclock, p->bt.max_pixelclock, + p->bt.standards, p->bt.capabilities); + break; + default: + pr_cont("type=%u\n", p->type); + break; + } +} + +static void v4l_print_frmsizeenum(const void *arg, bool write_only) +{ + const struct v4l2_frmsizeenum *p = arg; + + pr_cont("index=%u, pixelformat=%c%c%c%c, type=%u", + p->index, + (p->pixel_format & 0xff), + (p->pixel_format >> 8) & 0xff, + (p->pixel_format >> 16) & 0xff, + (p->pixel_format >> 24) & 0xff, + p->type); + switch (p->type) { + case V4L2_FRMSIZE_TYPE_DISCRETE: + pr_cont(" wxh=%ux%u\n", + p->discrete.width, p->discrete.height); + break; + case V4L2_FRMSIZE_TYPE_STEPWISE: + pr_cont(" min=%ux%u, max=%ux%u, step=%ux%u\n", + p->stepwise.min_width, p->stepwise.min_height, + p->stepwise.step_width, p->stepwise.step_height, + p->stepwise.max_width, p->stepwise.max_height); + break; + case V4L2_FRMSIZE_TYPE_CONTINUOUS: + /* fall through */ + default: + pr_cont("\n"); + break; + } +} + +static void v4l_print_frmivalenum(const void *arg, bool write_only) +{ + const struct v4l2_frmivalenum *p = arg; + + pr_cont("index=%u, pixelformat=%c%c%c%c, wxh=%ux%u, type=%u", + p->index, + (p->pixel_format & 0xff), + (p->pixel_format >> 8) & 0xff, + (p->pixel_format >> 16) & 0xff, + (p->pixel_format >> 24) & 0xff, + p->width, p->height, p->type); + switch (p->type) { + case V4L2_FRMIVAL_TYPE_DISCRETE: + pr_cont(" fps=%d/%d\n", + p->discrete.numerator, + p->discrete.denominator); + break; + case V4L2_FRMIVAL_TYPE_STEPWISE: + pr_cont(" min=%d/%d, max=%d/%d, step=%d/%d\n", + p->stepwise.min.numerator, + p->stepwise.min.denominator, + p->stepwise.max.numerator, + p->stepwise.max.denominator, + p->stepwise.step.numerator, + p->stepwise.step.denominator); + break; + case V4L2_FRMIVAL_TYPE_CONTINUOUS: + /* fall through */ + default: + pr_cont("\n"); + break; + } +} + +static void v4l_print_event(const void *arg, bool write_only) +{ + const struct v4l2_event *p = arg; + const struct v4l2_event_ctrl *c; + + pr_cont("type=0x%x, pending=%u, sequence=%u, id=%u, " + "timestamp=%lu.%9.9lu\n", + p->type, p->pending, p->sequence, p->id, + p->timestamp.tv_sec, p->timestamp.tv_nsec); + switch (p->type) { + case V4L2_EVENT_VSYNC: + printk(KERN_DEBUG "field=%s\n", + prt_names(p->u.vsync.field, v4l2_field_names)); + break; + case V4L2_EVENT_CTRL: + c = &p->u.ctrl; + printk(KERN_DEBUG "changes=0x%x, type=%u, ", + c->changes, c->type); + if (c->type == V4L2_CTRL_TYPE_INTEGER64) + pr_cont("value64=%lld, ", c->value64); + else + pr_cont("value=%d, ", c->value); + pr_cont("flags=0x%x, minimum=%d, maximum=%d, step=%d," + " default_value=%d\n", + c->flags, c->minimum, c->maximum, + c->step, c->default_value); + break; + case V4L2_EVENT_FRAME_SYNC: + pr_cont("frame_sequence=%u\n", + p->u.frame_sync.frame_sequence); + break; + } +} + +static void v4l_print_event_subscription(const void *arg, bool write_only) +{ + const struct v4l2_event_subscription *p = arg; + + pr_cont("type=0x%x, id=0x%x, flags=0x%x\n", + p->type, p->id, p->flags); +} + +static void v4l_print_sliced_vbi_cap(const void *arg, bool write_only) +{ + const struct v4l2_sliced_vbi_cap *p = arg; + int i; + + pr_cont("type=%s, service_set=0x%08x\n", + prt_names(p->type, v4l2_type_names), p->service_set); + for (i = 0; i < 24; i++) + printk(KERN_DEBUG "line[%02u]=0x%04x, 0x%04x\n", i, + p->service_lines[0][i], + p->service_lines[1][i]); +} + +static void v4l_print_freq_band(const void *arg, bool write_only) +{ + const struct v4l2_frequency_band *p = arg; + + pr_cont("tuner=%u, type=%u, index=%u, capability=0x%x, " + "rangelow=%u, rangehigh=%u, modulation=0x%x\n", + p->tuner, p->type, p->index, + p->capability, p->rangelow, + p->rangehigh, p->modulation); +} + +static void v4l_print_u32(const void *arg, bool write_only) +{ + pr_cont("value=%u\n", *(const u32 *)arg); +} + +static void v4l_print_newline(const void *arg, bool write_only) +{ + pr_cont("\n"); +} + +static void v4l_print_default(const void *arg, bool write_only) +{ + pr_cont("driver-specific ioctl\n"); +} + +static int check_ext_ctrls(struct v4l2_ext_controls *c, int allow_priv) +{ + __u32 i; + + /* zero the reserved fields */ + c->reserved[0] = c->reserved[1] = 0; + for (i = 0; i < c->count; i++) + c->controls[i].reserved2[0] = 0; + + /* V4L2_CID_PRIVATE_BASE cannot be used as control class + when using extended controls. + Only when passed in through VIDIOC_G_CTRL and VIDIOC_S_CTRL + is it allowed for backwards compatibility. + */ + if (!allow_priv && c->ctrl_class == V4L2_CID_PRIVATE_BASE) + return 0; + /* Check that all controls are from the same control class. */ + for (i = 0; i < c->count; i++) { + if (V4L2_CTRL_ID2CLASS(c->controls[i].id) != c->ctrl_class) { + c->error_idx = i; + return 0; + } + } + return 1; +} + +static int check_fmt(const struct v4l2_ioctl_ops *ops, enum v4l2_buf_type type) +{ + if (ops == NULL) + return -EINVAL; + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (ops->vidioc_g_fmt_vid_cap || + ops->vidioc_g_fmt_vid_cap_mplane) + return 0; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (ops->vidioc_g_fmt_vid_cap_mplane) + return 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (ops->vidioc_g_fmt_vid_overlay) + return 0; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (ops->vidioc_g_fmt_vid_out || + ops->vidioc_g_fmt_vid_out_mplane) + return 0; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (ops->vidioc_g_fmt_vid_out_mplane) + return 0; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (ops->vidioc_g_fmt_vid_out_overlay) + return 0; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (ops->vidioc_g_fmt_vbi_cap) + return 0; + break; + case V4L2_BUF_TYPE_VBI_OUTPUT: + if (ops->vidioc_g_fmt_vbi_out) + return 0; + break; + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + if (ops->vidioc_g_fmt_sliced_vbi_cap) + return 0; + break; + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + if (ops->vidioc_g_fmt_sliced_vbi_out) + return 0; + break; + case V4L2_BUF_TYPE_PRIVATE: + if (ops->vidioc_g_fmt_type_private) + return 0; + break; + } + return -EINVAL; +} + +static int v4l_querycap(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_capability *cap = (struct v4l2_capability *)arg; + + cap->version = LINUX_VERSION_CODE; + return ops->vidioc_querycap(file, fh, cap); +} + +static int v4l_s_input(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + return ops->vidioc_s_input(file, fh, *(unsigned int *)arg); +} + +static int v4l_s_output(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + return ops->vidioc_s_output(file, fh, *(unsigned int *)arg); +} + +static int v4l_g_priority(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd; + u32 *p = arg; + + if (ops->vidioc_g_priority) + return ops->vidioc_g_priority(file, fh, arg); + vfd = video_devdata(file); + *p = v4l2_prio_max(&vfd->v4l2_dev->prio); + return 0; +} + +static int v4l_s_priority(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd; + struct v4l2_fh *vfh; + u32 *p = arg; + + if (ops->vidioc_s_priority) + return ops->vidioc_s_priority(file, fh, *p); + vfd = video_devdata(file); + vfh = file->private_data; + return v4l2_prio_change(&vfd->v4l2_dev->prio, &vfh->prio, *p); +} + +static int v4l_enuminput(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_input *p = arg; + + /* + * We set the flags for CAP_PRESETS, CAP_CUSTOM_TIMINGS & + * CAP_STD here based on ioctl handler provided by the + * driver. If the driver doesn't support these + * for a specific input, it must override these flags. + */ + if (ops->vidioc_s_std) + p->capabilities |= V4L2_IN_CAP_STD; + if (ops->vidioc_s_dv_preset) + p->capabilities |= V4L2_IN_CAP_PRESETS; + if (ops->vidioc_s_dv_timings) + p->capabilities |= V4L2_IN_CAP_CUSTOM_TIMINGS; + + return ops->vidioc_enum_input(file, fh, p); +} + +static int v4l_enumoutput(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_output *p = arg; + + /* + * We set the flags for CAP_PRESETS, CAP_CUSTOM_TIMINGS & + * CAP_STD here based on ioctl handler provided by the + * driver. If the driver doesn't support these + * for a specific output, it must override these flags. + */ + if (ops->vidioc_s_std) + p->capabilities |= V4L2_OUT_CAP_STD; + if (ops->vidioc_s_dv_preset) + p->capabilities |= V4L2_OUT_CAP_PRESETS; + if (ops->vidioc_s_dv_timings) + p->capabilities |= V4L2_OUT_CAP_CUSTOM_TIMINGS; + + return ops->vidioc_enum_output(file, fh, p); +} + +static int v4l_enum_fmt(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_fmtdesc *p = arg; + + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (unlikely(!ops->vidioc_enum_fmt_vid_cap)) + break; + return ops->vidioc_enum_fmt_vid_cap(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (unlikely(!ops->vidioc_enum_fmt_vid_cap_mplane)) + break; + return ops->vidioc_enum_fmt_vid_cap_mplane(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (unlikely(!ops->vidioc_enum_fmt_vid_overlay)) + break; + return ops->vidioc_enum_fmt_vid_overlay(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (unlikely(!ops->vidioc_enum_fmt_vid_out)) + break; + return ops->vidioc_enum_fmt_vid_out(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (unlikely(!ops->vidioc_enum_fmt_vid_out_mplane)) + break; + return ops->vidioc_enum_fmt_vid_out_mplane(file, fh, arg); + case V4L2_BUF_TYPE_PRIVATE: + if (unlikely(!ops->vidioc_enum_fmt_type_private)) + break; + return ops->vidioc_enum_fmt_type_private(file, fh, arg); + } + return -EINVAL; +} + +static int v4l_g_fmt(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_format *p = arg; + + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (unlikely(!ops->vidioc_g_fmt_vid_cap)) + break; + return ops->vidioc_g_fmt_vid_cap(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (unlikely(!ops->vidioc_g_fmt_vid_cap_mplane)) + break; + return ops->vidioc_g_fmt_vid_cap_mplane(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (unlikely(!ops->vidioc_g_fmt_vid_overlay)) + break; + return ops->vidioc_g_fmt_vid_overlay(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (unlikely(!ops->vidioc_g_fmt_vid_out)) + break; + return ops->vidioc_g_fmt_vid_out(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (unlikely(!ops->vidioc_g_fmt_vid_out_mplane)) + break; + return ops->vidioc_g_fmt_vid_out_mplane(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (unlikely(!ops->vidioc_g_fmt_vid_out_overlay)) + break; + return ops->vidioc_g_fmt_vid_out_overlay(file, fh, arg); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (unlikely(!ops->vidioc_g_fmt_vbi_cap)) + break; + return ops->vidioc_g_fmt_vbi_cap(file, fh, arg); + case V4L2_BUF_TYPE_VBI_OUTPUT: + if (unlikely(!ops->vidioc_g_fmt_vbi_out)) + break; + return ops->vidioc_g_fmt_vbi_out(file, fh, arg); + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + if (unlikely(!ops->vidioc_g_fmt_sliced_vbi_cap)) + break; + return ops->vidioc_g_fmt_sliced_vbi_cap(file, fh, arg); + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + if (unlikely(!ops->vidioc_g_fmt_sliced_vbi_out)) + break; + return ops->vidioc_g_fmt_sliced_vbi_out(file, fh, arg); + case V4L2_BUF_TYPE_PRIVATE: + if (unlikely(!ops->vidioc_g_fmt_type_private)) + break; + return ops->vidioc_g_fmt_type_private(file, fh, arg); + } + return -EINVAL; +} + +static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_format *p = arg; + + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (unlikely(!ops->vidioc_s_fmt_vid_cap)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix); + return ops->vidioc_s_fmt_vid_cap(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (unlikely(!ops->vidioc_s_fmt_vid_cap_mplane)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix_mp); + return ops->vidioc_s_fmt_vid_cap_mplane(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (unlikely(!ops->vidioc_s_fmt_vid_overlay)) + break; + CLEAR_AFTER_FIELD(p, fmt.win); + return ops->vidioc_s_fmt_vid_overlay(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (unlikely(!ops->vidioc_s_fmt_vid_out)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix); + return ops->vidioc_s_fmt_vid_out(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (unlikely(!ops->vidioc_s_fmt_vid_out_mplane)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix_mp); + return ops->vidioc_s_fmt_vid_out_mplane(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (unlikely(!ops->vidioc_s_fmt_vid_out_overlay)) + break; + CLEAR_AFTER_FIELD(p, fmt.win); + return ops->vidioc_s_fmt_vid_out_overlay(file, fh, arg); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (unlikely(!ops->vidioc_s_fmt_vbi_cap)) + break; + CLEAR_AFTER_FIELD(p, fmt.vbi); + return ops->vidioc_s_fmt_vbi_cap(file, fh, arg); + case V4L2_BUF_TYPE_VBI_OUTPUT: + if (unlikely(!ops->vidioc_s_fmt_vbi_out)) + break; + CLEAR_AFTER_FIELD(p, fmt.vbi); + return ops->vidioc_s_fmt_vbi_out(file, fh, arg); + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_cap)) + break; + CLEAR_AFTER_FIELD(p, fmt.sliced); + return ops->vidioc_s_fmt_sliced_vbi_cap(file, fh, arg); + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_out)) + break; + CLEAR_AFTER_FIELD(p, fmt.sliced); + return ops->vidioc_s_fmt_sliced_vbi_out(file, fh, arg); + case V4L2_BUF_TYPE_PRIVATE: + if (unlikely(!ops->vidioc_s_fmt_type_private)) + break; + return ops->vidioc_s_fmt_type_private(file, fh, arg); + } + return -EINVAL; +} + +static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_format *p = arg; + + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (unlikely(!ops->vidioc_try_fmt_vid_cap)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix); + return ops->vidioc_try_fmt_vid_cap(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (unlikely(!ops->vidioc_try_fmt_vid_cap_mplane)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix_mp); + return ops->vidioc_try_fmt_vid_cap_mplane(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (unlikely(!ops->vidioc_try_fmt_vid_overlay)) + break; + CLEAR_AFTER_FIELD(p, fmt.win); + return ops->vidioc_try_fmt_vid_overlay(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (unlikely(!ops->vidioc_try_fmt_vid_out)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix); + return ops->vidioc_try_fmt_vid_out(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (unlikely(!ops->vidioc_try_fmt_vid_out_mplane)) + break; + CLEAR_AFTER_FIELD(p, fmt.pix_mp); + return ops->vidioc_try_fmt_vid_out_mplane(file, fh, arg); + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (unlikely(!ops->vidioc_try_fmt_vid_out_overlay)) + break; + CLEAR_AFTER_FIELD(p, fmt.win); + return ops->vidioc_try_fmt_vid_out_overlay(file, fh, arg); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (unlikely(!ops->vidioc_try_fmt_vbi_cap)) + break; + CLEAR_AFTER_FIELD(p, fmt.vbi); + return ops->vidioc_try_fmt_vbi_cap(file, fh, arg); + case V4L2_BUF_TYPE_VBI_OUTPUT: + if (unlikely(!ops->vidioc_try_fmt_vbi_out)) + break; + CLEAR_AFTER_FIELD(p, fmt.vbi); + return ops->vidioc_try_fmt_vbi_out(file, fh, arg); + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_cap)) + break; + CLEAR_AFTER_FIELD(p, fmt.sliced); + return ops->vidioc_try_fmt_sliced_vbi_cap(file, fh, arg); + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_out)) + break; + CLEAR_AFTER_FIELD(p, fmt.sliced); + return ops->vidioc_try_fmt_sliced_vbi_out(file, fh, arg); + case V4L2_BUF_TYPE_PRIVATE: + if (unlikely(!ops->vidioc_try_fmt_type_private)) + break; + return ops->vidioc_try_fmt_type_private(file, fh, arg); + } + return -EINVAL; +} + +static int v4l_streamon(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + return ops->vidioc_streamon(file, fh, *(unsigned int *)arg); +} + +static int v4l_streamoff(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + return ops->vidioc_streamoff(file, fh, *(unsigned int *)arg); +} + +static int v4l_g_tuner(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_tuner *p = arg; + int err; + + p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + err = ops->vidioc_g_tuner(file, fh, p); + if (!err) + p->capability |= V4L2_TUNER_CAP_FREQ_BANDS; + return err; +} + +static int v4l_s_tuner(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_tuner *p = arg; + + p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + return ops->vidioc_s_tuner(file, fh, p); +} + +static int v4l_g_modulator(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_modulator *p = arg; + int err; + + err = ops->vidioc_g_modulator(file, fh, p); + if (!err) + p->capability |= V4L2_TUNER_CAP_FREQ_BANDS; + return err; +} + +static int v4l_g_frequency(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_frequency *p = arg; + + p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + return ops->vidioc_g_frequency(file, fh, p); +} + +static int v4l_s_frequency(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_frequency *p = arg; + enum v4l2_tuner_type type; + + type = (vfd->vfl_type == VFL_TYPE_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + if (p->type != type) + return -EINVAL; + return ops->vidioc_s_frequency(file, fh, p); +} + +static int v4l_enumstd(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_standard *p = arg; + v4l2_std_id id = vfd->tvnorms, curr_id = 0; + unsigned int index = p->index, i, j = 0; + const char *descr = ""; + + /* Return norm array in a canonical way */ + for (i = 0; i <= index && id; i++) { + /* last std value in the standards array is 0, so this + while always ends there since (id & 0) == 0. */ + while ((id & standards[j].std) != standards[j].std) + j++; + curr_id = standards[j].std; + descr = standards[j].descr; + j++; + if (curr_id == 0) + break; + if (curr_id != V4L2_STD_PAL && + curr_id != V4L2_STD_SECAM && + curr_id != V4L2_STD_NTSC) + id &= ~curr_id; + } + if (i <= index) + return -EINVAL; + + v4l2_video_std_construct(p, curr_id, descr); + return 0; +} + +static int v4l_g_std(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + v4l2_std_id *id = arg; + + /* Calls the specific handler */ + if (ops->vidioc_g_std) + return ops->vidioc_g_std(file, fh, arg); + if (vfd->current_norm) { + *id = vfd->current_norm; + return 0; + } + return -ENOTTY; +} + +static int v4l_s_std(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + v4l2_std_id *id = arg, norm; + int ret; + + norm = (*id) & vfd->tvnorms; + if (vfd->tvnorms && !norm) /* Check if std is supported */ + return -EINVAL; + + /* Calls the specific handler */ + ret = ops->vidioc_s_std(file, fh, &norm); + + /* Updates standard information */ + if (ret >= 0) + vfd->current_norm = norm; + return ret; +} + +static int v4l_querystd(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + v4l2_std_id *p = arg; + + /* + * If nothing detected, it should return all supported + * standard. + * Drivers just need to mask the std argument, in order + * to remove the standards that don't apply from the mask. + * This means that tuners, audio and video decoders can join + * their efforts to improve the standards detection. + */ + *p = vfd->tvnorms; + return ops->vidioc_querystd(file, fh, arg); +} + +static int v4l_s_hw_freq_seek(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_hw_freq_seek *p = arg; + enum v4l2_tuner_type type; + + type = (vfd->vfl_type == VFL_TYPE_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + if (p->type != type) + return -EINVAL; + return ops->vidioc_s_hw_freq_seek(file, fh, p); +} + +static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_requestbuffers *p = arg; + int ret = check_fmt(ops, p->type); + + if (ret) + return ret; + + if (p->type < V4L2_BUF_TYPE_PRIVATE) + CLEAR_AFTER_FIELD(p, memory); + + return ops->vidioc_reqbufs(file, fh, p); +} + +static int v4l_querybuf(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_buffer *p = arg; + int ret = check_fmt(ops, p->type); + + return ret ? ret : ops->vidioc_querybuf(file, fh, p); +} + +static int v4l_qbuf(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_buffer *p = arg; + int ret = check_fmt(ops, p->type); + + return ret ? ret : ops->vidioc_qbuf(file, fh, p); +} + +static int v4l_dqbuf(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_buffer *p = arg; + int ret = check_fmt(ops, p->type); + + return ret ? ret : ops->vidioc_dqbuf(file, fh, p); +} + +static int v4l_create_bufs(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_create_buffers *create = arg; + int ret = check_fmt(ops, create->format.type); + + return ret ? ret : ops->vidioc_create_bufs(file, fh, create); +} + +static int v4l_prepare_buf(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_buffer *b = arg; + int ret = check_fmt(ops, b->type); + + return ret ? ret : ops->vidioc_prepare_buf(file, fh, b); +} + +static int v4l_g_parm(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_streamparm *p = arg; + v4l2_std_id std; + int ret = check_fmt(ops, p->type); + + if (ret) + return ret; + if (ops->vidioc_g_parm) + return ops->vidioc_g_parm(file, fh, p); + std = vfd->current_norm; + if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + p->parm.capture.readbuffers = 2; + if (ops->vidioc_g_std) + ret = ops->vidioc_g_std(file, fh, &std); + if (ret == 0) + v4l2_video_std_frame_period(std, + &p->parm.capture.timeperframe); + return ret; +} + +static int v4l_s_parm(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_streamparm *p = arg; + int ret = check_fmt(ops, p->type); + + return ret ? ret : ops->vidioc_s_parm(file, fh, p); +} + +static int v4l_queryctrl(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_queryctrl *p = arg; + struct v4l2_fh *vfh = + test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL; + + if (vfh && vfh->ctrl_handler) + return v4l2_queryctrl(vfh->ctrl_handler, p); + if (vfd->ctrl_handler) + return v4l2_queryctrl(vfd->ctrl_handler, p); + if (ops->vidioc_queryctrl) + return ops->vidioc_queryctrl(file, fh, p); + return -ENOTTY; +} + +static int v4l_querymenu(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_querymenu *p = arg; + struct v4l2_fh *vfh = + test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL; + + if (vfh && vfh->ctrl_handler) + return v4l2_querymenu(vfh->ctrl_handler, p); + if (vfd->ctrl_handler) + return v4l2_querymenu(vfd->ctrl_handler, p); + if (ops->vidioc_querymenu) + return ops->vidioc_querymenu(file, fh, p); + return -ENOTTY; +} + +static int v4l_g_ctrl(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_control *p = arg; + struct v4l2_fh *vfh = + test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL; + struct v4l2_ext_controls ctrls; + struct v4l2_ext_control ctrl; + + if (vfh && vfh->ctrl_handler) + return v4l2_g_ctrl(vfh->ctrl_handler, p); + if (vfd->ctrl_handler) + return v4l2_g_ctrl(vfd->ctrl_handler, p); + if (ops->vidioc_g_ctrl) + return ops->vidioc_g_ctrl(file, fh, p); + if (ops->vidioc_g_ext_ctrls == NULL) + return -ENOTTY; + + ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(p->id); + ctrls.count = 1; + ctrls.controls = &ctrl; + ctrl.id = p->id; + ctrl.value = p->value; + if (check_ext_ctrls(&ctrls, 1)) { + int ret = ops->vidioc_g_ext_ctrls(file, fh, &ctrls); + + if (ret == 0) + p->value = ctrl.value; + return ret; + } + return -EINVAL; +} + +static int v4l_s_ctrl(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_control *p = arg; + struct v4l2_fh *vfh = + test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL; + struct v4l2_ext_controls ctrls; + struct v4l2_ext_control ctrl; + + if (vfh && vfh->ctrl_handler) + return v4l2_s_ctrl(vfh, vfh->ctrl_handler, p); + if (vfd->ctrl_handler) + return v4l2_s_ctrl(NULL, vfd->ctrl_handler, p); + if (ops->vidioc_s_ctrl) + return ops->vidioc_s_ctrl(file, fh, p); + if (ops->vidioc_s_ext_ctrls == NULL) + return -ENOTTY; + + ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(p->id); + ctrls.count = 1; + ctrls.controls = &ctrl; + ctrl.id = p->id; + ctrl.value = p->value; + if (check_ext_ctrls(&ctrls, 1)) + return ops->vidioc_s_ext_ctrls(file, fh, &ctrls); + return -EINVAL; +} + +static int v4l_g_ext_ctrls(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_ext_controls *p = arg; + struct v4l2_fh *vfh = + test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL; + + p->error_idx = p->count; + if (vfh && vfh->ctrl_handler) + return v4l2_g_ext_ctrls(vfh->ctrl_handler, p); + if (vfd->ctrl_handler) + return v4l2_g_ext_ctrls(vfd->ctrl_handler, p); + if (ops->vidioc_g_ext_ctrls == NULL) + return -ENOTTY; + return check_ext_ctrls(p, 0) ? ops->vidioc_g_ext_ctrls(file, fh, p) : + -EINVAL; +} + +static int v4l_s_ext_ctrls(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_ext_controls *p = arg; + struct v4l2_fh *vfh = + test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL; + + p->error_idx = p->count; + if (vfh && vfh->ctrl_handler) + return v4l2_s_ext_ctrls(vfh, vfh->ctrl_handler, p); + if (vfd->ctrl_handler) + return v4l2_s_ext_ctrls(NULL, vfd->ctrl_handler, p); + if (ops->vidioc_s_ext_ctrls == NULL) + return -ENOTTY; + return check_ext_ctrls(p, 0) ? ops->vidioc_s_ext_ctrls(file, fh, p) : + -EINVAL; +} + +static int v4l_try_ext_ctrls(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_ext_controls *p = arg; + struct v4l2_fh *vfh = + test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) ? fh : NULL; + + p->error_idx = p->count; + if (vfh && vfh->ctrl_handler) + return v4l2_try_ext_ctrls(vfh->ctrl_handler, p); + if (vfd->ctrl_handler) + return v4l2_try_ext_ctrls(vfd->ctrl_handler, p); + if (ops->vidioc_try_ext_ctrls == NULL) + return -ENOTTY; + return check_ext_ctrls(p, 0) ? ops->vidioc_try_ext_ctrls(file, fh, p) : + -EINVAL; +} + +static int v4l_g_crop(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_crop *p = arg; + struct v4l2_selection s = { + .type = p->type, + }; + int ret; + + if (ops->vidioc_g_crop) + return ops->vidioc_g_crop(file, fh, p); + /* simulate capture crop using selection api */ + + /* crop means compose for output devices */ + if (V4L2_TYPE_IS_OUTPUT(p->type)) + s.target = V4L2_SEL_TGT_COMPOSE_ACTIVE; + else + s.target = V4L2_SEL_TGT_CROP_ACTIVE; + + ret = ops->vidioc_g_selection(file, fh, &s); + + /* copying results to old structure on success */ + if (!ret) + p->c = s.r; + return ret; +} + +static int v4l_s_crop(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_crop *p = arg; + struct v4l2_selection s = { + .type = p->type, + .r = p->c, + }; + + if (ops->vidioc_s_crop) + return ops->vidioc_s_crop(file, fh, p); + /* simulate capture crop using selection api */ + + /* crop means compose for output devices */ + if (V4L2_TYPE_IS_OUTPUT(p->type)) + s.target = V4L2_SEL_TGT_COMPOSE_ACTIVE; + else + s.target = V4L2_SEL_TGT_CROP_ACTIVE; + + return ops->vidioc_s_selection(file, fh, &s); +} + +static int v4l_cropcap(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_cropcap *p = arg; + struct v4l2_selection s = { .type = p->type }; + int ret; + + if (ops->vidioc_cropcap) + return ops->vidioc_cropcap(file, fh, p); + + /* obtaining bounds */ + if (V4L2_TYPE_IS_OUTPUT(p->type)) + s.target = V4L2_SEL_TGT_COMPOSE_BOUNDS; + else + s.target = V4L2_SEL_TGT_CROP_BOUNDS; + + ret = ops->vidioc_g_selection(file, fh, &s); + if (ret) + return ret; + p->bounds = s.r; + + /* obtaining defrect */ + if (V4L2_TYPE_IS_OUTPUT(p->type)) + s.target = V4L2_SEL_TGT_COMPOSE_DEFAULT; + else + s.target = V4L2_SEL_TGT_CROP_DEFAULT; + + ret = ops->vidioc_g_selection(file, fh, &s); + if (ret) + return ret; + p->defrect = s.r; + + /* setting trivial pixelaspect */ + p->pixelaspect.numerator = 1; + p->pixelaspect.denominator = 1; + return 0; +} + +static int v4l_log_status(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + int ret; + + if (vfd->v4l2_dev) + pr_info("%s: ================= START STATUS =================\n", + vfd->v4l2_dev->name); + ret = ops->vidioc_log_status(file, fh); + if (vfd->v4l2_dev) + pr_info("%s: ================== END STATUS ==================\n", + vfd->v4l2_dev->name); + return ret; +} + +static int v4l_dbg_g_register(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ +#ifdef CONFIG_VIDEO_ADV_DEBUG + struct v4l2_dbg_register *p = arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return ops->vidioc_g_register(file, fh, p); +#else + return -ENOTTY; +#endif +} + +static int v4l_dbg_s_register(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ +#ifdef CONFIG_VIDEO_ADV_DEBUG + struct v4l2_dbg_register *p = arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return ops->vidioc_s_register(file, fh, p); +#else + return -ENOTTY; +#endif +} + +static int v4l_dbg_g_chip_ident(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_dbg_chip_ident *p = arg; + + p->ident = V4L2_IDENT_NONE; + p->revision = 0; + return ops->vidioc_g_chip_ident(file, fh, p); +} + +static int v4l_dqevent(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + return v4l2_event_dequeue(fh, arg, file->f_flags & O_NONBLOCK); +} + +static int v4l_subscribe_event(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + return ops->vidioc_subscribe_event(fh, arg); +} + +static int v4l_unsubscribe_event(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + return ops->vidioc_unsubscribe_event(fh, arg); +} + +static int v4l_g_sliced_vbi_cap(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct v4l2_sliced_vbi_cap *p = arg; + + /* Clear up to type, everything after type is zeroed already */ + memset(p, 0, offsetof(struct v4l2_sliced_vbi_cap, type)); + + return ops->vidioc_g_sliced_vbi_cap(file, fh, p); +} + +static int v4l_enum_freq_bands(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *arg) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_frequency_band *p = arg; + enum v4l2_tuner_type type; + int err; + + type = (vfd->vfl_type == VFL_TYPE_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + + if (type != p->type) + return -EINVAL; + if (ops->vidioc_enum_freq_bands) + return ops->vidioc_enum_freq_bands(file, fh, p); + if (ops->vidioc_g_tuner) { + struct v4l2_tuner t = { + .index = p->tuner, + .type = type, + }; + + err = ops->vidioc_g_tuner(file, fh, &t); + if (err) + return err; + p->capability = t.capability | V4L2_TUNER_CAP_FREQ_BANDS; + p->rangelow = t.rangelow; + p->rangehigh = t.rangehigh; + p->modulation = (type == V4L2_TUNER_RADIO) ? + V4L2_BAND_MODULATION_FM : V4L2_BAND_MODULATION_VSB; + return 0; + } + if (ops->vidioc_g_modulator) { + struct v4l2_modulator m = { + .index = p->tuner, + }; + + if (type != V4L2_TUNER_RADIO) + return -EINVAL; + err = ops->vidioc_g_modulator(file, fh, &m); + if (err) + return err; + p->capability = m.capability | V4L2_TUNER_CAP_FREQ_BANDS; + p->rangelow = m.rangelow; + p->rangehigh = m.rangehigh; + p->modulation = (type == V4L2_TUNER_RADIO) ? + V4L2_BAND_MODULATION_FM : V4L2_BAND_MODULATION_VSB; + return 0; + } + return -ENOTTY; +} + +struct v4l2_ioctl_info { + unsigned int ioctl; + u32 flags; + const char * const name; + union { + u32 offset; + int (*func)(const struct v4l2_ioctl_ops *ops, + struct file *file, void *fh, void *p); + } u; + void (*debug)(const void *arg, bool write_only); +}; + +/* This control needs a priority check */ +#define INFO_FL_PRIO (1 << 0) +/* This control can be valid if the filehandle passes a control handler. */ +#define INFO_FL_CTRL (1 << 1) +/* This is a standard ioctl, no need for special code */ +#define INFO_FL_STD (1 << 2) +/* This is ioctl has its own function */ +#define INFO_FL_FUNC (1 << 3) +/* Queuing ioctl */ +#define INFO_FL_QUEUE (1 << 4) +/* Zero struct from after the field to the end */ +#define INFO_FL_CLEAR(v4l2_struct, field) \ + ((offsetof(struct v4l2_struct, field) + \ + sizeof(((struct v4l2_struct *)0)->field)) << 16) +#define INFO_FL_CLEAR_MASK (_IOC_SIZEMASK << 16) + +#define IOCTL_INFO_STD(_ioctl, _vidioc, _debug, _flags) \ + [_IOC_NR(_ioctl)] = { \ + .ioctl = _ioctl, \ + .flags = _flags | INFO_FL_STD, \ + .name = #_ioctl, \ + .u.offset = offsetof(struct v4l2_ioctl_ops, _vidioc), \ + .debug = _debug, \ + } + +#define IOCTL_INFO_FNC(_ioctl, _func, _debug, _flags) \ + [_IOC_NR(_ioctl)] = { \ + .ioctl = _ioctl, \ + .flags = _flags | INFO_FL_FUNC, \ + .name = #_ioctl, \ + .u.func = _func, \ + .debug = _debug, \ + } + +static struct v4l2_ioctl_info v4l2_ioctls[] = { + IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0), + IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)), + IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, INFO_FL_CLEAR(v4l2_format, type)), + IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE), + IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)), + IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf, v4l_print_framebuffer, 0), + IOCTL_INFO_STD(VIDIOC_S_FBUF, vidioc_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_OVERLAY, vidioc_overlay, v4l_print_u32, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE), + IOCTL_INFO_FNC(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE), + IOCTL_INFO_FNC(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE), + IOCTL_INFO_FNC(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE), + IOCTL_INFO_FNC(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)), + IOCTL_INFO_FNC(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_G_STD, v4l_g_std, v4l_print_std, 0), + IOCTL_INFO_FNC(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)), + IOCTL_INFO_FNC(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)), + IOCTL_INFO_FNC(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)), + IOCTL_INFO_FNC(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL), + IOCTL_INFO_FNC(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)), + IOCTL_INFO_FNC(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_G_AUDIO, vidioc_g_audio, v4l_print_audio, 0), + IOCTL_INFO_STD(VIDIOC_S_AUDIO, vidioc_s_audio, v4l_print_audio, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_QUERYCTRL, v4l_queryctrl, v4l_print_queryctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_queryctrl, id)), + IOCTL_INFO_FNC(VIDIOC_QUERYMENU, v4l_querymenu, v4l_print_querymenu, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_querymenu, index)), + IOCTL_INFO_STD(VIDIOC_G_INPUT, vidioc_g_input, v4l_print_u32, 0), + IOCTL_INFO_FNC(VIDIOC_S_INPUT, v4l_s_input, v4l_print_u32, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_G_OUTPUT, vidioc_g_output, v4l_print_u32, 0), + IOCTL_INFO_FNC(VIDIOC_S_OUTPUT, v4l_s_output, v4l_print_u32, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_ENUMOUTPUT, v4l_enumoutput, v4l_print_enumoutput, INFO_FL_CLEAR(v4l2_output, index)), + IOCTL_INFO_STD(VIDIOC_G_AUDOUT, vidioc_g_audout, v4l_print_audioout, 0), + IOCTL_INFO_STD(VIDIOC_S_AUDOUT, vidioc_s_audout, v4l_print_audioout, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_G_MODULATOR, v4l_g_modulator, v4l_print_modulator, INFO_FL_CLEAR(v4l2_modulator, index)), + IOCTL_INFO_STD(VIDIOC_S_MODULATOR, vidioc_s_modulator, v4l_print_modulator, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_G_FREQUENCY, v4l_g_frequency, v4l_print_frequency, INFO_FL_CLEAR(v4l2_frequency, tuner)), + IOCTL_INFO_FNC(VIDIOC_S_FREQUENCY, v4l_s_frequency, v4l_print_frequency, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_CROPCAP, v4l_cropcap, v4l_print_cropcap, INFO_FL_CLEAR(v4l2_cropcap, type)), + IOCTL_INFO_FNC(VIDIOC_G_CROP, v4l_g_crop, v4l_print_crop, INFO_FL_CLEAR(v4l2_crop, type)), + IOCTL_INFO_FNC(VIDIOC_S_CROP, v4l_s_crop, v4l_print_crop, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_G_SELECTION, vidioc_g_selection, v4l_print_selection, 0), + IOCTL_INFO_STD(VIDIOC_S_SELECTION, vidioc_s_selection, v4l_print_selection, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_G_JPEGCOMP, vidioc_g_jpegcomp, v4l_print_jpegcompression, 0), + IOCTL_INFO_STD(VIDIOC_S_JPEGCOMP, vidioc_s_jpegcomp, v4l_print_jpegcompression, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_QUERYSTD, v4l_querystd, v4l_print_std, 0), + IOCTL_INFO_FNC(VIDIOC_TRY_FMT, v4l_try_fmt, v4l_print_format, 0), + IOCTL_INFO_STD(VIDIOC_ENUMAUDIO, vidioc_enumaudio, v4l_print_audio, INFO_FL_CLEAR(v4l2_audio, index)), + IOCTL_INFO_STD(VIDIOC_ENUMAUDOUT, vidioc_enumaudout, v4l_print_audioout, INFO_FL_CLEAR(v4l2_audioout, index)), + IOCTL_INFO_FNC(VIDIOC_G_PRIORITY, v4l_g_priority, v4l_print_u32, 0), + IOCTL_INFO_FNC(VIDIOC_S_PRIORITY, v4l_s_priority, v4l_print_u32, INFO_FL_PRIO), + IOCTL_INFO_FNC(VIDIOC_G_SLICED_VBI_CAP, v4l_g_sliced_vbi_cap, v4l_print_sliced_vbi_cap, INFO_FL_CLEAR(v4l2_sliced_vbi_cap, type)), + IOCTL_INFO_FNC(VIDIOC_LOG_STATUS, v4l_log_status, v4l_print_newline, 0), + IOCTL_INFO_FNC(VIDIOC_G_EXT_CTRLS, v4l_g_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL), + IOCTL_INFO_FNC(VIDIOC_S_EXT_CTRLS, v4l_s_ext_ctrls, v4l_print_ext_controls, INFO_FL_PRIO | INFO_FL_CTRL), + IOCTL_INFO_FNC(VIDIOC_TRY_EXT_CTRLS, v4l_try_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL), + IOCTL_INFO_STD(VIDIOC_ENUM_FRAMESIZES, vidioc_enum_framesizes, v4l_print_frmsizeenum, INFO_FL_CLEAR(v4l2_frmsizeenum, pixel_format)), + IOCTL_INFO_STD(VIDIOC_ENUM_FRAMEINTERVALS, vidioc_enum_frameintervals, v4l_print_frmivalenum, INFO_FL_CLEAR(v4l2_frmivalenum, height)), + IOCTL_INFO_STD(VIDIOC_G_ENC_INDEX, vidioc_g_enc_index, v4l_print_enc_idx, 0), + IOCTL_INFO_STD(VIDIOC_ENCODER_CMD, vidioc_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_encoder_cmd, flags)), + IOCTL_INFO_STD(VIDIOC_TRY_ENCODER_CMD, vidioc_try_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_CLEAR(v4l2_encoder_cmd, flags)), + IOCTL_INFO_STD(VIDIOC_DECODER_CMD, vidioc_decoder_cmd, v4l_print_decoder_cmd, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_TRY_DECODER_CMD, vidioc_try_decoder_cmd, v4l_print_decoder_cmd, 0), + IOCTL_INFO_FNC(VIDIOC_DBG_S_REGISTER, v4l_dbg_s_register, v4l_print_dbg_register, 0), + IOCTL_INFO_FNC(VIDIOC_DBG_G_REGISTER, v4l_dbg_g_register, v4l_print_dbg_register, 0), + IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_IDENT, v4l_dbg_g_chip_ident, v4l_print_dbg_chip_ident, 0), + IOCTL_INFO_FNC(VIDIOC_S_HW_FREQ_SEEK, v4l_s_hw_freq_seek, v4l_print_hw_freq_seek, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_ENUM_DV_PRESETS, vidioc_enum_dv_presets, v4l_print_dv_enum_presets, 0), + IOCTL_INFO_STD(VIDIOC_S_DV_PRESET, vidioc_s_dv_preset, v4l_print_dv_preset, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_G_DV_PRESET, vidioc_g_dv_preset, v4l_print_dv_preset, 0), + IOCTL_INFO_STD(VIDIOC_QUERY_DV_PRESET, vidioc_query_dv_preset, v4l_print_dv_preset, 0), + IOCTL_INFO_STD(VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings, v4l_print_dv_timings, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings, v4l_print_dv_timings, 0), + IOCTL_INFO_FNC(VIDIOC_DQEVENT, v4l_dqevent, v4l_print_event, 0), + IOCTL_INFO_FNC(VIDIOC_SUBSCRIBE_EVENT, v4l_subscribe_event, v4l_print_event_subscription, 0), + IOCTL_INFO_FNC(VIDIOC_UNSUBSCRIBE_EVENT, v4l_unsubscribe_event, v4l_print_event_subscription, 0), + IOCTL_INFO_FNC(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE), + IOCTL_INFO_FNC(VIDIOC_PREPARE_BUF, v4l_prepare_buf, v4l_print_buffer, INFO_FL_QUEUE), + IOCTL_INFO_STD(VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings, v4l_print_enum_dv_timings, 0), + IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, 0), + IOCTL_INFO_STD(VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, type)), + IOCTL_INFO_FNC(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0), +}; +#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) + +bool v4l2_is_known_ioctl(unsigned int cmd) +{ + if (_IOC_NR(cmd) >= V4L2_IOCTLS) + return false; + return v4l2_ioctls[_IOC_NR(cmd)].ioctl == cmd; +} + +struct mutex *v4l2_ioctl_get_lock(struct video_device *vdev, unsigned cmd) +{ + if (_IOC_NR(cmd) >= V4L2_IOCTLS) + return vdev->lock; + if (test_bit(_IOC_NR(cmd), vdev->disable_locking)) + return NULL; + if (vdev->queue && vdev->queue->lock && + (v4l2_ioctls[_IOC_NR(cmd)].flags & INFO_FL_QUEUE)) + return vdev->queue->lock; + return vdev->lock; +} + +/* Common ioctl debug function. This function can be used by + external ioctl messages as well as internal V4L ioctl */ +void v4l_printk_ioctl(const char *prefix, unsigned int cmd) +{ + const char *dir, *type; + + if (prefix) + printk(KERN_DEBUG "%s: ", prefix); + + switch (_IOC_TYPE(cmd)) { + case 'd': + type = "v4l2_int"; + break; + case 'V': + if (_IOC_NR(cmd) >= V4L2_IOCTLS) { + type = "v4l2"; + break; + } + pr_cont("%s", v4l2_ioctls[_IOC_NR(cmd)].name); + return; + default: + type = "unknown"; + break; + } + + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: dir = "--"; break; + case _IOC_READ: dir = "r-"; break; + case _IOC_WRITE: dir = "-w"; break; + case _IOC_READ | _IOC_WRITE: dir = "rw"; break; + default: dir = "*ERR*"; break; + } + pr_cont("%s ioctl '%c', dir=%s, #%d (0x%08x)", + type, _IOC_TYPE(cmd), dir, _IOC_NR(cmd), cmd); +} +EXPORT_SYMBOL(v4l_printk_ioctl); + +static long __video_do_ioctl(struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vfd = video_devdata(file); + const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops; + bool write_only = false; + struct v4l2_ioctl_info default_info; + const struct v4l2_ioctl_info *info; + void *fh = file->private_data; + struct v4l2_fh *vfh = NULL; + int use_fh_prio = 0; + int debug = vfd->debug; + long ret = -ENOTTY; + + if (ops == NULL) { + pr_warn("%s: has no ioctl_ops.\n", + video_device_node_name(vfd)); + return ret; + } + + if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) { + vfh = file->private_data; + use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); + } + + if (v4l2_is_known_ioctl(cmd)) { + info = &v4l2_ioctls[_IOC_NR(cmd)]; + + if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) && + !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler)) + goto done; + + if (use_fh_prio && (info->flags & INFO_FL_PRIO)) { + ret = v4l2_prio_check(vfd->prio, vfh->prio); + if (ret) + goto done; + } + } else { + default_info.ioctl = cmd; + default_info.flags = 0; + default_info.debug = v4l_print_default; + info = &default_info; + } + + write_only = _IOC_DIR(cmd) == _IOC_WRITE; + if (write_only && debug > V4L2_DEBUG_IOCTL) { + v4l_printk_ioctl(video_device_node_name(vfd), cmd); + pr_cont(": "); + info->debug(arg, write_only); + } + if (info->flags & INFO_FL_STD) { + typedef int (*vidioc_op)(struct file *file, void *fh, void *p); + const void *p = vfd->ioctl_ops; + const vidioc_op *vidioc = p + info->u.offset; + + ret = (*vidioc)(file, fh, arg); + } else if (info->flags & INFO_FL_FUNC) { + ret = info->u.func(ops, file, fh, arg); + } else if (!ops->vidioc_default) { + ret = -ENOTTY; + } else { + ret = ops->vidioc_default(file, fh, + use_fh_prio ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0, + cmd, arg); + } + +done: + if (debug) { + if (write_only && debug > V4L2_DEBUG_IOCTL) { + if (ret < 0) + printk(KERN_DEBUG "%s: error %ld\n", + video_device_node_name(vfd), ret); + return ret; + } + v4l_printk_ioctl(video_device_node_name(vfd), cmd); + if (ret < 0) + pr_cont(": error %ld\n", ret); + else if (debug == V4L2_DEBUG_IOCTL) + pr_cont("\n"); + else if (_IOC_DIR(cmd) == _IOC_NONE) + info->debug(arg, write_only); + else { + pr_cont(": "); + info->debug(arg, write_only); + } + } + + return ret; +} + +static int check_array_args(unsigned int cmd, void *parg, size_t *array_size, + void * __user *user_ptr, void ***kernel_ptr) +{ + int ret = 0; + + switch (cmd) { + case VIDIOC_QUERYBUF: + case VIDIOC_QBUF: + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = parg; + + if (V4L2_TYPE_IS_MULTIPLANAR(buf->type) && buf->length > 0) { + if (buf->length > VIDEO_MAX_PLANES) { + ret = -EINVAL; + break; + } + *user_ptr = (void __user *)buf->m.planes; + *kernel_ptr = (void *)&buf->m.planes; + *array_size = sizeof(struct v4l2_plane) * buf->length; + ret = 1; + } + break; + } + + case VIDIOC_S_EXT_CTRLS: + case VIDIOC_G_EXT_CTRLS: + case VIDIOC_TRY_EXT_CTRLS: { + struct v4l2_ext_controls *ctrls = parg; + + if (ctrls->count != 0) { + if (ctrls->count > V4L2_CID_MAX_CTRLS) { + ret = -EINVAL; + break; + } + *user_ptr = (void __user *)ctrls->controls; + *kernel_ptr = (void *)&ctrls->controls; + *array_size = sizeof(struct v4l2_ext_control) + * ctrls->count; + ret = 1; + } + break; + } + } + + return ret; +} + +long +video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, + v4l2_kioctl func) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = (void *)arg; + long err = -EINVAL; + bool has_array_args; + size_t array_size = 0; + void __user *user_ptr = NULL; + void **kernel_ptr = NULL; + + /* Copy arguments into temp kernel buffer */ + if (_IOC_DIR(cmd) != _IOC_NONE) { + 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 (_IOC_DIR(cmd) & _IOC_WRITE) { + unsigned int n = _IOC_SIZE(cmd); + + /* + * In some cases, only a few fields are used as input, + * i.e. when the app sets "index" and then the driver + * fills in the rest of the structure for the thing + * with that index. We only need to copy up the first + * non-input field. + */ + if (v4l2_is_known_ioctl(cmd)) { + u32 flags = v4l2_ioctls[_IOC_NR(cmd)].flags; + if (flags & INFO_FL_CLEAR_MASK) + n = (flags & INFO_FL_CLEAR_MASK) >> 16; + } + + if (copy_from_user(parg, (void __user *)arg, n)) + goto out; + + /* zero out anything we don't copy from userspace */ + if (n < _IOC_SIZE(cmd)) + memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n); + } else { + /* read-only ioctl */ + memset(parg, 0, _IOC_SIZE(cmd)); + } + } + + err = check_array_args(cmd, parg, &array_size, &user_ptr, &kernel_ptr); + if (err < 0) + goto out; + has_array_args = err; + + if (has_array_args) { + /* + * When adding new types of array args, make sure that the + * parent argument to ioctl (which contains the pointer to the + * array) fits into sbuf (so that mbuf will still remain + * unused up to here). + */ + mbuf = kmalloc(array_size, GFP_KERNEL); + err = -ENOMEM; + if (NULL == mbuf) + goto out_array_args; + err = -EFAULT; + if (copy_from_user(mbuf, user_ptr, array_size)) + goto out_array_args; + *kernel_ptr = mbuf; + } + + /* Handles IOCTL */ + err = func(file, cmd, parg); + if (err == -ENOIOCTLCMD) + err = -ENOTTY; + + if (has_array_args) { + *kernel_ptr = user_ptr; + if (copy_to_user(user_ptr, mbuf, array_size)) + err = -EFAULT; + goto out_array_args; + } + /* VIDIOC_QUERY_DV_TIMINGS can return an error, but still have valid + results that must be returned. */ + if (err < 0 && cmd != VIDIOC_QUERY_DV_TIMINGS) + goto out; + +out_array_args: + /* 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; +} +EXPORT_SYMBOL(video_usercopy); + +long video_ioctl2(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(file, cmd, arg, __video_do_ioctl); +} +EXPORT_SYMBOL(video_ioctl2); diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c new file mode 100644 index 000000000000..97b48318aee1 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -0,0 +1,647 @@ +/* + * Memory-to-memory device framework for Video for Linux 2 and videobuf. + * + * Helper functions for devices that use videobuf buffers for both their + * source and destination. + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + * + * 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. + */ +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <media/videobuf2-core.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> + +MODULE_DESCRIPTION("Mem to mem device framework for videobuf"); +MODULE_AUTHOR("Pawel Osciak, <pawel@osciak.com>"); +MODULE_LICENSE("GPL"); + +static bool debug; +module_param(debug, bool, 0644); + +#define dprintk(fmt, arg...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "%s: " fmt, __func__, ## arg);\ + } while (0) + + +/* Instance is already queued on the job_queue */ +#define TRANS_QUEUED (1 << 0) +/* Instance is currently running in hardware */ +#define TRANS_RUNNING (1 << 1) + + +/* Offset base for buffers on the destination queue - used to distinguish + * between source and destination buffers when mmapping - they receive the same + * offsets but for different queues */ +#define DST_QUEUE_OFF_BASE (1 << 30) + + +/** + * struct v4l2_m2m_dev - per-device context + * @curr_ctx: currently running instance + * @job_queue: instances queued to run + * @job_spinlock: protects job_queue + * @m2m_ops: driver callbacks + */ +struct v4l2_m2m_dev { + struct v4l2_m2m_ctx *curr_ctx; + + struct list_head job_queue; + spinlock_t job_spinlock; + + struct v4l2_m2m_ops *m2m_ops; +}; + +static struct v4l2_m2m_queue_ctx *get_queue_ctx(struct v4l2_m2m_ctx *m2m_ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &m2m_ctx->out_q_ctx; + else + return &m2m_ctx->cap_q_ctx; +} + +/** + * v4l2_m2m_get_vq() - return vb2_queue for the given type + */ +struct vb2_queue *v4l2_m2m_get_vq(struct v4l2_m2m_ctx *m2m_ctx, + enum v4l2_buf_type type) +{ + struct v4l2_m2m_queue_ctx *q_ctx; + + q_ctx = get_queue_ctx(m2m_ctx, type); + if (!q_ctx) + return NULL; + + return &q_ctx->q; +} +EXPORT_SYMBOL(v4l2_m2m_get_vq); + +/** + * v4l2_m2m_next_buf() - return next buffer from the list of ready buffers + */ +void *v4l2_m2m_next_buf(struct v4l2_m2m_queue_ctx *q_ctx) +{ + struct v4l2_m2m_buffer *b = NULL; + unsigned long flags; + + spin_lock_irqsave(&q_ctx->rdy_spinlock, flags); + + if (list_empty(&q_ctx->rdy_queue)) { + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); + return NULL; + } + + b = list_entry(q_ctx->rdy_queue.next, struct v4l2_m2m_buffer, list); + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); + return &b->vb; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_next_buf); + +/** + * v4l2_m2m_buf_remove() - take off a buffer from the list of ready buffers and + * return it + */ +void *v4l2_m2m_buf_remove(struct v4l2_m2m_queue_ctx *q_ctx) +{ + struct v4l2_m2m_buffer *b = NULL; + unsigned long flags; + + spin_lock_irqsave(&q_ctx->rdy_spinlock, flags); + if (list_empty(&q_ctx->rdy_queue)) { + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); + return NULL; + } + b = list_entry(q_ctx->rdy_queue.next, struct v4l2_m2m_buffer, list); + list_del(&b->list); + q_ctx->num_rdy--; + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); + + return &b->vb; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_buf_remove); + +/* + * Scheduling handlers + */ + +/** + * v4l2_m2m_get_curr_priv() - return driver private data for the currently + * running instance or NULL if no instance is running + */ +void *v4l2_m2m_get_curr_priv(struct v4l2_m2m_dev *m2m_dev) +{ + unsigned long flags; + void *ret = NULL; + + spin_lock_irqsave(&m2m_dev->job_spinlock, flags); + if (m2m_dev->curr_ctx) + ret = m2m_dev->curr_ctx->priv; + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + + return ret; +} +EXPORT_SYMBOL(v4l2_m2m_get_curr_priv); + +/** + * v4l2_m2m_try_run() - select next job to perform and run it if possible + * + * Get next transaction (if present) from the waiting jobs list and run it. + */ +static void v4l2_m2m_try_run(struct v4l2_m2m_dev *m2m_dev) +{ + unsigned long flags; + + spin_lock_irqsave(&m2m_dev->job_spinlock, flags); + if (NULL != m2m_dev->curr_ctx) { + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + dprintk("Another instance is running, won't run now\n"); + return; + } + + if (list_empty(&m2m_dev->job_queue)) { + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + dprintk("No job pending\n"); + return; + } + + m2m_dev->curr_ctx = list_entry(m2m_dev->job_queue.next, + struct v4l2_m2m_ctx, queue); + m2m_dev->curr_ctx->job_flags |= TRANS_RUNNING; + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + + m2m_dev->m2m_ops->device_run(m2m_dev->curr_ctx->priv); +} + +/** + * v4l2_m2m_try_schedule() - check whether an instance is ready to be added to + * the pending job queue and add it if so. + * @m2m_ctx: m2m context assigned to the instance to be checked + * + * There are three basic requirements an instance has to meet to be able to run: + * 1) at least one source buffer has to be queued, + * 2) at least one destination buffer has to be queued, + * 3) streaming has to be on. + * + * There may also be additional, custom requirements. In such case the driver + * should supply a custom callback (job_ready in v4l2_m2m_ops) that should + * return 1 if the instance is ready. + * An example of the above could be an instance that requires more than one + * src/dst buffer per transaction. + */ +static void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx) +{ + struct v4l2_m2m_dev *m2m_dev; + unsigned long flags_job, flags; + + m2m_dev = m2m_ctx->m2m_dev; + dprintk("Trying to schedule a job for m2m_ctx: %p\n", m2m_ctx); + + if (!m2m_ctx->out_q_ctx.q.streaming + || !m2m_ctx->cap_q_ctx.q.streaming) { + dprintk("Streaming needs to be on for both queues\n"); + return; + } + + spin_lock_irqsave(&m2m_dev->job_spinlock, flags_job); + if (m2m_ctx->job_flags & TRANS_QUEUED) { + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); + dprintk("On job queue already\n"); + return; + } + + spin_lock_irqsave(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); + if (list_empty(&m2m_ctx->out_q_ctx.rdy_queue)) { + spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); + dprintk("No input buffers available\n"); + return; + } + if (list_empty(&m2m_ctx->cap_q_ctx.rdy_queue)) { + spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); + dprintk("No output buffers available\n"); + return; + } + spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); + + if (m2m_dev->m2m_ops->job_ready + && (!m2m_dev->m2m_ops->job_ready(m2m_ctx->priv))) { + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); + dprintk("Driver not ready\n"); + return; + } + + list_add_tail(&m2m_ctx->queue, &m2m_dev->job_queue); + m2m_ctx->job_flags |= TRANS_QUEUED; + + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); + + v4l2_m2m_try_run(m2m_dev); +} + +/** + * v4l2_m2m_job_finish() - inform the framework that a job has been finished + * and have it clean up + * + * Called by a driver to yield back the device after it has finished with it. + * Should be called as soon as possible after reaching a state which allows + * other instances to take control of the device. + * + * This function has to be called only after device_run() callback has been + * called on the driver. To prevent recursion, it should not be called directly + * from the device_run() callback though. + */ +void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev, + struct v4l2_m2m_ctx *m2m_ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&m2m_dev->job_spinlock, flags); + if (!m2m_dev->curr_ctx || m2m_dev->curr_ctx != m2m_ctx) { + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + dprintk("Called by an instance not currently running\n"); + return; + } + + list_del(&m2m_dev->curr_ctx->queue); + m2m_dev->curr_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING); + wake_up(&m2m_dev->curr_ctx->finished); + m2m_dev->curr_ctx = NULL; + + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + + /* This instance might have more buffers ready, but since we do not + * allow more than one job on the job_queue per instance, each has + * to be scheduled separately after the previous one finishes. */ + v4l2_m2m_try_schedule(m2m_ctx); + v4l2_m2m_try_run(m2m_dev); +} +EXPORT_SYMBOL(v4l2_m2m_job_finish); + +/** + * v4l2_m2m_reqbufs() - multi-queue-aware REQBUFS multiplexer + */ +int v4l2_m2m_reqbufs(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + struct v4l2_requestbuffers *reqbufs) +{ + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(m2m_ctx, reqbufs->type); + return vb2_reqbufs(vq, reqbufs); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_reqbufs); + +/** + * v4l2_m2m_querybuf() - multi-queue-aware QUERYBUF multiplexer + * + * See v4l2_m2m_mmap() documentation for details. + */ +int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + struct v4l2_buffer *buf) +{ + struct vb2_queue *vq; + int ret = 0; + unsigned int i; + + vq = v4l2_m2m_get_vq(m2m_ctx, buf->type); + ret = vb2_querybuf(vq, buf); + + /* Adjust MMAP memory offsets for the CAPTURE queue */ + if (buf->memory == V4L2_MEMORY_MMAP && !V4L2_TYPE_IS_OUTPUT(vq->type)) { + if (V4L2_TYPE_IS_MULTIPLANAR(vq->type)) { + for (i = 0; i < buf->length; ++i) + buf->m.planes[i].m.mem_offset + += DST_QUEUE_OFF_BASE; + } else { + buf->m.offset += DST_QUEUE_OFF_BASE; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_querybuf); + +/** + * v4l2_m2m_qbuf() - enqueue a source or destination buffer, depending on + * the type + */ +int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + struct v4l2_buffer *buf) +{ + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(m2m_ctx, buf->type); + ret = vb2_qbuf(vq, buf); + if (!ret) + v4l2_m2m_try_schedule(m2m_ctx); + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_qbuf); + +/** + * v4l2_m2m_dqbuf() - dequeue a source or destination buffer, depending on + * the type + */ +int v4l2_m2m_dqbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + struct v4l2_buffer *buf) +{ + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(m2m_ctx, buf->type); + return vb2_dqbuf(vq, buf, file->f_flags & O_NONBLOCK); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_dqbuf); + +/** + * v4l2_m2m_streamon() - turn on streaming for a video queue + */ +int v4l2_m2m_streamon(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + enum v4l2_buf_type type) +{ + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(m2m_ctx, type); + ret = vb2_streamon(vq, type); + if (!ret) + v4l2_m2m_try_schedule(m2m_ctx); + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_streamon); + +/** + * v4l2_m2m_streamoff() - turn off streaming for a video queue + */ +int v4l2_m2m_streamoff(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + enum v4l2_buf_type type) +{ + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(m2m_ctx, type); + return vb2_streamoff(vq, type); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_streamoff); + +/** + * v4l2_m2m_poll() - poll replacement, for destination buffers only + * + * Call from the driver's poll() function. Will poll both queues. If a buffer + * is available to dequeue (with dqbuf) from the source queue, this will + * indicate that a non-blocking write can be performed, while read will be + * returned in case of the destination queue. + */ +unsigned int v4l2_m2m_poll(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + struct poll_table_struct *wait) +{ + struct video_device *vfd = video_devdata(file); + unsigned long req_events = poll_requested_events(wait); + struct vb2_queue *src_q, *dst_q; + struct vb2_buffer *src_vb = NULL, *dst_vb = NULL; + unsigned int rc = 0; + unsigned long flags; + + if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) { + struct v4l2_fh *fh = file->private_data; + + if (v4l2_event_pending(fh)) + rc = POLLPRI; + else if (req_events & POLLPRI) + poll_wait(file, &fh->wait, wait); + if (!(req_events & (POLLOUT | POLLWRNORM | POLLIN | POLLRDNORM))) + return rc; + } + + src_q = v4l2_m2m_get_src_vq(m2m_ctx); + dst_q = v4l2_m2m_get_dst_vq(m2m_ctx); + + /* + * There has to be at least one buffer queued on each queued_list, which + * means either in driver already or waiting for driver to claim it + * and start processing. + */ + if ((!src_q->streaming || list_empty(&src_q->queued_list)) + && (!dst_q->streaming || list_empty(&dst_q->queued_list))) { + rc |= POLLERR; + goto end; + } + + if (m2m_ctx->m2m_dev->m2m_ops->unlock) + m2m_ctx->m2m_dev->m2m_ops->unlock(m2m_ctx->priv); + + poll_wait(file, &src_q->done_wq, wait); + poll_wait(file, &dst_q->done_wq, wait); + + if (m2m_ctx->m2m_dev->m2m_ops->lock) + m2m_ctx->m2m_dev->m2m_ops->lock(m2m_ctx->priv); + + spin_lock_irqsave(&src_q->done_lock, flags); + if (!list_empty(&src_q->done_list)) + src_vb = list_first_entry(&src_q->done_list, struct vb2_buffer, + done_entry); + if (src_vb && (src_vb->state == VB2_BUF_STATE_DONE + || src_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLOUT | POLLWRNORM; + spin_unlock_irqrestore(&src_q->done_lock, flags); + + spin_lock_irqsave(&dst_q->done_lock, flags); + if (!list_empty(&dst_q->done_list)) + dst_vb = list_first_entry(&dst_q->done_list, struct vb2_buffer, + done_entry); + if (dst_vb && (dst_vb->state == VB2_BUF_STATE_DONE + || dst_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLIN | POLLRDNORM; + spin_unlock_irqrestore(&dst_q->done_lock, flags); + +end: + return rc; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_poll); + +/** + * v4l2_m2m_mmap() - source and destination queues-aware mmap multiplexer + * + * Call from driver's mmap() function. Will handle mmap() for both queues + * seamlessly for videobuffer, which will receive normal per-queue offsets and + * proper videobuf queue pointers. The differentiation is made outside videobuf + * by adding a predefined offset to buffers from one of the queues and + * subtracting it before passing it back to videobuf. Only drivers (and + * thus applications) receive modified offsets. + */ +int v4l2_m2m_mmap(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, + struct vm_area_struct *vma) +{ + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct vb2_queue *vq; + + if (offset < DST_QUEUE_OFF_BASE) { + vq = v4l2_m2m_get_src_vq(m2m_ctx); + } else { + vq = v4l2_m2m_get_dst_vq(m2m_ctx); + vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT); + } + + return vb2_mmap(vq, vma); +} +EXPORT_SYMBOL(v4l2_m2m_mmap); + +/** + * v4l2_m2m_init() - initialize per-driver m2m data + * + * Usually called from driver's probe() function. + */ +struct v4l2_m2m_dev *v4l2_m2m_init(struct v4l2_m2m_ops *m2m_ops) +{ + struct v4l2_m2m_dev *m2m_dev; + + if (!m2m_ops) + return ERR_PTR(-EINVAL); + + BUG_ON(!m2m_ops->device_run); + BUG_ON(!m2m_ops->job_abort); + + m2m_dev = kzalloc(sizeof *m2m_dev, GFP_KERNEL); + if (!m2m_dev) + return ERR_PTR(-ENOMEM); + + m2m_dev->curr_ctx = NULL; + m2m_dev->m2m_ops = m2m_ops; + INIT_LIST_HEAD(&m2m_dev->job_queue); + spin_lock_init(&m2m_dev->job_spinlock); + + return m2m_dev; +} +EXPORT_SYMBOL_GPL(v4l2_m2m_init); + +/** + * v4l2_m2m_release() - cleans up and frees a m2m_dev structure + * + * Usually called from driver's remove() function. + */ +void v4l2_m2m_release(struct v4l2_m2m_dev *m2m_dev) +{ + kfree(m2m_dev); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_release); + +/** + * v4l2_m2m_ctx_init() - allocate and initialize a m2m context + * @priv - driver's instance private data + * @m2m_dev - a previously initialized m2m_dev struct + * @vq_init - a callback for queue type-specific initialization function to be + * used for initializing videobuf_queues + * + * Usually called from driver's open() function. + */ +struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(struct v4l2_m2m_dev *m2m_dev, + void *drv_priv, + int (*queue_init)(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)) +{ + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_m2m_queue_ctx *out_q_ctx, *cap_q_ctx; + int ret; + + m2m_ctx = kzalloc(sizeof *m2m_ctx, GFP_KERNEL); + if (!m2m_ctx) + return ERR_PTR(-ENOMEM); + + m2m_ctx->priv = drv_priv; + m2m_ctx->m2m_dev = m2m_dev; + init_waitqueue_head(&m2m_ctx->finished); + + out_q_ctx = &m2m_ctx->out_q_ctx; + cap_q_ctx = &m2m_ctx->cap_q_ctx; + + INIT_LIST_HEAD(&out_q_ctx->rdy_queue); + INIT_LIST_HEAD(&cap_q_ctx->rdy_queue); + spin_lock_init(&out_q_ctx->rdy_spinlock); + spin_lock_init(&cap_q_ctx->rdy_spinlock); + + INIT_LIST_HEAD(&m2m_ctx->queue); + + ret = queue_init(drv_priv, &out_q_ctx->q, &cap_q_ctx->q); + + if (ret) + goto err; + + return m2m_ctx; +err: + kfree(m2m_ctx); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_init); + +/** + * v4l2_m2m_ctx_release() - release m2m context + * + * Usually called from driver's release() function. + */ +void v4l2_m2m_ctx_release(struct v4l2_m2m_ctx *m2m_ctx) +{ + struct v4l2_m2m_dev *m2m_dev; + unsigned long flags; + + m2m_dev = m2m_ctx->m2m_dev; + + spin_lock_irqsave(&m2m_dev->job_spinlock, flags); + if (m2m_ctx->job_flags & TRANS_RUNNING) { + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + m2m_dev->m2m_ops->job_abort(m2m_ctx->priv); + dprintk("m2m_ctx %p running, will wait to complete", m2m_ctx); + wait_event(m2m_ctx->finished, !(m2m_ctx->job_flags & TRANS_RUNNING)); + } else if (m2m_ctx->job_flags & TRANS_QUEUED) { + list_del(&m2m_ctx->queue); + m2m_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING); + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + dprintk("m2m_ctx: %p had been on queue and was removed\n", + m2m_ctx); + } else { + /* Do nothing, was not on queue/running */ + spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); + } + + vb2_queue_release(&m2m_ctx->cap_q_ctx.q); + vb2_queue_release(&m2m_ctx->out_q_ctx.q); + + kfree(m2m_ctx); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_release); + +/** + * v4l2_m2m_buf_queue() - add a buffer to the proper ready buffers list. + * + * Call from buf_queue(), videobuf_queue_ops callback. + */ +void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, struct vb2_buffer *vb) +{ + struct v4l2_m2m_buffer *b = container_of(vb, struct v4l2_m2m_buffer, vb); + struct v4l2_m2m_queue_ctx *q_ctx; + unsigned long flags; + + q_ctx = get_queue_ctx(m2m_ctx, vb->vb2_queue->type); + if (!q_ctx) + return; + + spin_lock_irqsave(&q_ctx->rdy_spinlock, flags); + list_add_tail(&b->list, &q_ctx->rdy_queue); + q_ctx->num_rdy++; + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_buf_queue); + diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c new file mode 100644 index 000000000000..9182f81deb5b --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -0,0 +1,470 @@ +/* + * V4L2 sub-device + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * Sakari Ailus <sakari.ailus@iki.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 + */ + +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/videodev2.h> +#include <linux/export.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> + +static int subdev_fh_init(struct v4l2_subdev_fh *fh, struct v4l2_subdev *sd) +{ +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + fh->pad = kzalloc(sizeof(*fh->pad) * sd->entity.num_pads, GFP_KERNEL); + if (fh->pad == NULL) + return -ENOMEM; +#endif + return 0; +} + +static void subdev_fh_free(struct v4l2_subdev_fh *fh) +{ +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + kfree(fh->pad); + fh->pad = NULL; +#endif +} + +static int subdev_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_subdev_fh *subdev_fh; +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_entity *entity = NULL; +#endif + int ret; + + subdev_fh = kzalloc(sizeof(*subdev_fh), GFP_KERNEL); + if (subdev_fh == NULL) + return -ENOMEM; + + ret = subdev_fh_init(subdev_fh, sd); + if (ret) { + kfree(subdev_fh); + return ret; + } + + v4l2_fh_init(&subdev_fh->vfh, vdev); + v4l2_fh_add(&subdev_fh->vfh); + file->private_data = &subdev_fh->vfh; +#if defined(CONFIG_MEDIA_CONTROLLER) + if (sd->v4l2_dev->mdev) { + entity = media_entity_get(&sd->entity); + if (!entity) { + ret = -EBUSY; + goto err; + } + } +#endif + + if (sd->internal_ops && sd->internal_ops->open) { + ret = sd->internal_ops->open(sd, subdev_fh); + if (ret < 0) + goto err; + } + + return 0; + +err: +#if defined(CONFIG_MEDIA_CONTROLLER) + if (entity) + media_entity_put(entity); +#endif + v4l2_fh_del(&subdev_fh->vfh); + v4l2_fh_exit(&subdev_fh->vfh); + subdev_fh_free(subdev_fh); + kfree(subdev_fh); + + return ret; +} + +static int subdev_close(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_fh *vfh = file->private_data; + struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh); + + if (sd->internal_ops && sd->internal_ops->close) + sd->internal_ops->close(sd, subdev_fh); +#if defined(CONFIG_MEDIA_CONTROLLER) + if (sd->v4l2_dev->mdev) + media_entity_put(&sd->entity); +#endif + v4l2_fh_del(vfh); + v4l2_fh_exit(vfh); + subdev_fh_free(subdev_fh); + kfree(subdev_fh); + file->private_data = NULL; + + return 0; +} + +static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_fh *vfh = file->private_data; +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh); +#endif + + switch (cmd) { + case VIDIOC_QUERYCTRL: + return v4l2_queryctrl(vfh->ctrl_handler, arg); + + case VIDIOC_QUERYMENU: + return v4l2_querymenu(vfh->ctrl_handler, arg); + + case VIDIOC_G_CTRL: + return v4l2_g_ctrl(vfh->ctrl_handler, arg); + + case VIDIOC_S_CTRL: + return v4l2_s_ctrl(vfh, vfh->ctrl_handler, arg); + + case VIDIOC_G_EXT_CTRLS: + return v4l2_g_ext_ctrls(vfh->ctrl_handler, arg); + + case VIDIOC_S_EXT_CTRLS: + return v4l2_s_ext_ctrls(vfh, vfh->ctrl_handler, arg); + + case VIDIOC_TRY_EXT_CTRLS: + return v4l2_try_ext_ctrls(vfh->ctrl_handler, arg); + + case VIDIOC_DQEVENT: + if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS)) + return -ENOIOCTLCMD; + + return v4l2_event_dequeue(vfh, arg, file->f_flags & O_NONBLOCK); + + case VIDIOC_SUBSCRIBE_EVENT: + return v4l2_subdev_call(sd, core, subscribe_event, vfh, arg); + + case VIDIOC_UNSUBSCRIBE_EVENT: + return v4l2_subdev_call(sd, core, unsubscribe_event, vfh, arg); + +#ifdef CONFIG_VIDEO_ADV_DEBUG + case VIDIOC_DBG_G_REGISTER: + { + struct v4l2_dbg_register *p = arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return v4l2_subdev_call(sd, core, g_register, p); + } + case VIDIOC_DBG_S_REGISTER: + { + struct v4l2_dbg_register *p = arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return v4l2_subdev_call(sd, core, s_register, p); + } +#endif + + case VIDIOC_LOG_STATUS: { + int ret; + + pr_info("%s: ================= START STATUS =================\n", + sd->name); + ret = v4l2_subdev_call(sd, core, log_status); + pr_info("%s: ================== END STATUS ==================\n", + sd->name); + return ret; + } + +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + case VIDIOC_SUBDEV_G_FMT: { + struct v4l2_subdev_format *format = arg; + + if (format->which != V4L2_SUBDEV_FORMAT_TRY && + format->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (format->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, get_fmt, subdev_fh, format); + } + + case VIDIOC_SUBDEV_S_FMT: { + struct v4l2_subdev_format *format = arg; + + if (format->which != V4L2_SUBDEV_FORMAT_TRY && + format->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (format->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, set_fmt, subdev_fh, format); + } + + case VIDIOC_SUBDEV_G_CROP: { + struct v4l2_subdev_crop *crop = arg; + struct v4l2_subdev_selection sel; + int rval; + + if (crop->which != V4L2_SUBDEV_FORMAT_TRY && + crop->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (crop->pad >= sd->entity.num_pads) + return -EINVAL; + + rval = v4l2_subdev_call(sd, pad, get_crop, subdev_fh, crop); + if (rval != -ENOIOCTLCMD) + return rval; + + memset(&sel, 0, sizeof(sel)); + sel.which = crop->which; + sel.pad = crop->pad; + sel.target = V4L2_SEL_TGT_CROP; + + rval = v4l2_subdev_call( + sd, pad, get_selection, subdev_fh, &sel); + + crop->rect = sel.r; + + return rval; + } + + case VIDIOC_SUBDEV_S_CROP: { + struct v4l2_subdev_crop *crop = arg; + struct v4l2_subdev_selection sel; + int rval; + + if (crop->which != V4L2_SUBDEV_FORMAT_TRY && + crop->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (crop->pad >= sd->entity.num_pads) + return -EINVAL; + + rval = v4l2_subdev_call(sd, pad, set_crop, subdev_fh, crop); + if (rval != -ENOIOCTLCMD) + return rval; + + memset(&sel, 0, sizeof(sel)); + sel.which = crop->which; + sel.pad = crop->pad; + sel.target = V4L2_SEL_TGT_CROP; + sel.r = crop->rect; + + rval = v4l2_subdev_call( + sd, pad, set_selection, subdev_fh, &sel); + + crop->rect = sel.r; + + return rval; + } + + case VIDIOC_SUBDEV_ENUM_MBUS_CODE: { + struct v4l2_subdev_mbus_code_enum *code = arg; + + if (code->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, enum_mbus_code, subdev_fh, + code); + } + + case VIDIOC_SUBDEV_ENUM_FRAME_SIZE: { + struct v4l2_subdev_frame_size_enum *fse = arg; + + if (fse->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, enum_frame_size, subdev_fh, + fse); + } + + case VIDIOC_SUBDEV_G_FRAME_INTERVAL: + return v4l2_subdev_call(sd, video, g_frame_interval, arg); + + case VIDIOC_SUBDEV_S_FRAME_INTERVAL: + return v4l2_subdev_call(sd, video, s_frame_interval, arg); + + case VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: { + struct v4l2_subdev_frame_interval_enum *fie = arg; + + if (fie->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call(sd, pad, enum_frame_interval, subdev_fh, + fie); + } + + case VIDIOC_SUBDEV_G_SELECTION: { + struct v4l2_subdev_selection *sel = arg; + + if (sel->which != V4L2_SUBDEV_FORMAT_TRY && + sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (sel->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call( + sd, pad, get_selection, subdev_fh, sel); + } + + case VIDIOC_SUBDEV_S_SELECTION: { + struct v4l2_subdev_selection *sel = arg; + + if (sel->which != V4L2_SUBDEV_FORMAT_TRY && + sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (sel->pad >= sd->entity.num_pads) + return -EINVAL; + + return v4l2_subdev_call( + sd, pad, set_selection, subdev_fh, sel); + } +#endif + default: + return v4l2_subdev_call(sd, core, ioctl, cmd, arg); + } + + return 0; +} + +static long subdev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return video_usercopy(file, cmd, arg, subdev_do_ioctl); +} + +static unsigned int subdev_poll(struct file *file, poll_table *wait) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); + struct v4l2_fh *fh = file->private_data; + + if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS)) + return POLLERR; + + poll_wait(file, &fh->wait, wait); + + if (v4l2_event_pending(fh)) + return POLLPRI; + + return 0; +} + +const struct v4l2_file_operations v4l2_subdev_fops = { + .owner = THIS_MODULE, + .open = subdev_open, + .unlocked_ioctl = subdev_ioctl, + .release = subdev_close, + .poll = subdev_poll, +}; + +#ifdef CONFIG_MEDIA_CONTROLLER +int v4l2_subdev_link_validate_default(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + if (source_fmt->format.width != sink_fmt->format.width + || source_fmt->format.height != sink_fmt->format.height + || source_fmt->format.code != sink_fmt->format.code) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate_default); + +static int +v4l2_subdev_link_validate_get_format(struct media_pad *pad, + struct v4l2_subdev_format *fmt) +{ + switch (media_entity_type(pad->entity)) { + case MEDIA_ENT_T_V4L2_SUBDEV: + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt->pad = pad->index; + return v4l2_subdev_call(media_entity_to_v4l2_subdev( + pad->entity), + pad, get_fmt, NULL, fmt); + default: + WARN(1, "Driver bug! Wrong media entity type %d, entity %s\n", + media_entity_type(pad->entity), pad->entity->name); + /* Fall through */ + case MEDIA_ENT_T_DEVNODE_V4L: + return -EINVAL; + } +} + +int v4l2_subdev_link_validate(struct media_link *link) +{ + struct v4l2_subdev *sink; + struct v4l2_subdev_format sink_fmt, source_fmt; + int rval; + + rval = v4l2_subdev_link_validate_get_format( + link->source, &source_fmt); + if (rval < 0) + return 0; + + rval = v4l2_subdev_link_validate_get_format( + link->sink, &sink_fmt); + if (rval < 0) + return 0; + + sink = media_entity_to_v4l2_subdev(link->sink->entity); + + rval = v4l2_subdev_call(sink, pad, link_validate, link, + &source_fmt, &sink_fmt); + if (rval != -ENOIOCTLCMD) + return rval; + + return v4l2_subdev_link_validate_default( + sink, link, &source_fmt, &sink_fmt); +} +EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate); +#endif /* CONFIG_MEDIA_CONTROLLER */ + +void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops) +{ + INIT_LIST_HEAD(&sd->list); + BUG_ON(!ops); + sd->ops = ops; + sd->v4l2_dev = NULL; + sd->flags = 0; + sd->name[0] = '\0'; + sd->grp_id = 0; + sd->dev_priv = NULL; + sd->host_priv = NULL; +#if defined(CONFIG_MEDIA_CONTROLLER) + sd->entity.name = sd->name; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV; +#endif +} +EXPORT_SYMBOL(v4l2_subdev_init); diff --git a/drivers/media/v4l2-core/videobuf-core.c b/drivers/media/v4l2-core/videobuf-core.c new file mode 100644 index 000000000000..bf7a326b1cdc --- /dev/null +++ b/drivers/media/v4l2-core/videobuf-core.c @@ -0,0 +1,1189 @@ +/* + * generic helper functions for handling video4linux capture buffers + * + * (c) 2007 Mauro Carvalho Chehab, <mchehab@infradead.org> + * + * Highly based on video-buf written originally by: + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> + * (c) 2006 Mauro Carvalho Chehab, <mchehab@infradead.org> + * (c) 2006 Ted Walther and John Sokol + * + * 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 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + +#include <media/videobuf-core.h> + +#define MAGIC_BUFFER 0x20070728 +#define MAGIC_CHECK(is, should) \ + do { \ + if (unlikely((is) != (should))) { \ + printk(KERN_ERR \ + "magic mismatch: %x (expected %x)\n", \ + is, should); \ + BUG(); \ + } \ + } while (0) + +static int debug; +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("helper module to manage video4linux buffers"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>"); +MODULE_LICENSE("GPL"); + +#define dprintk(level, fmt, arg...) \ + do { \ + if (debug >= level) \ + printk(KERN_DEBUG "vbuf: " fmt, ## arg); \ + } while (0) + +/* --------------------------------------------------------------------- */ + +#define CALL(q, f, arg...) \ + ((q->int_ops->f) ? q->int_ops->f(arg) : 0) + +struct videobuf_buffer *videobuf_alloc_vb(struct videobuf_queue *q) +{ + struct videobuf_buffer *vb; + + BUG_ON(q->msize < sizeof(*vb)); + + if (!q->int_ops || !q->int_ops->alloc_vb) { + printk(KERN_ERR "No specific ops defined!\n"); + BUG(); + } + + vb = q->int_ops->alloc_vb(q->msize); + if (NULL != vb) { + init_waitqueue_head(&vb->done); + vb->magic = MAGIC_BUFFER; + } + + return vb; +} +EXPORT_SYMBOL_GPL(videobuf_alloc_vb); + +static int is_state_active_or_queued(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + unsigned long flags; + bool rc; + + spin_lock_irqsave(q->irqlock, flags); + rc = vb->state != VIDEOBUF_ACTIVE && vb->state != VIDEOBUF_QUEUED; + spin_unlock_irqrestore(q->irqlock, flags); + return rc; +}; + +int videobuf_waiton(struct videobuf_queue *q, struct videobuf_buffer *vb, + int non_blocking, int intr) +{ + bool is_ext_locked; + int ret = 0; + + MAGIC_CHECK(vb->magic, MAGIC_BUFFER); + + if (non_blocking) { + if (is_state_active_or_queued(q, vb)) + return 0; + return -EAGAIN; + } + + is_ext_locked = q->ext_lock && mutex_is_locked(q->ext_lock); + + /* Release vdev lock to prevent this wait from blocking outside access to + the device. */ + if (is_ext_locked) + mutex_unlock(q->ext_lock); + if (intr) + ret = wait_event_interruptible(vb->done, is_state_active_or_queued(q, vb)); + else + wait_event(vb->done, is_state_active_or_queued(q, vb)); + /* Relock */ + if (is_ext_locked) + mutex_lock(q->ext_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(videobuf_waiton); + +int videobuf_iolock(struct videobuf_queue *q, struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + MAGIC_CHECK(vb->magic, MAGIC_BUFFER); + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + return CALL(q, iolock, q, vb, fbuf); +} +EXPORT_SYMBOL_GPL(videobuf_iolock); + +void *videobuf_queue_to_vaddr(struct videobuf_queue *q, + struct videobuf_buffer *buf) +{ + if (q->int_ops->vaddr) + return q->int_ops->vaddr(buf); + return NULL; +} +EXPORT_SYMBOL_GPL(videobuf_queue_to_vaddr); + +/* --------------------------------------------------------------------- */ + + +void videobuf_queue_core_init(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct videobuf_qtype_ops *int_ops, + struct mutex *ext_lock) +{ + BUG_ON(!q); + memset(q, 0, sizeof(*q)); + q->irqlock = irqlock; + q->ext_lock = ext_lock; + q->dev = dev; + q->type = type; + q->field = field; + q->msize = msize; + q->ops = ops; + q->priv_data = priv; + q->int_ops = int_ops; + + /* All buffer operations are mandatory */ + BUG_ON(!q->ops->buf_setup); + BUG_ON(!q->ops->buf_prepare); + BUG_ON(!q->ops->buf_queue); + BUG_ON(!q->ops->buf_release); + + /* Lock is mandatory for queue_cancel to work */ + BUG_ON(!irqlock); + + /* Having implementations for abstract methods are mandatory */ + BUG_ON(!q->int_ops); + + mutex_init(&q->vb_lock); + init_waitqueue_head(&q->wait); + INIT_LIST_HEAD(&q->stream); +} +EXPORT_SYMBOL_GPL(videobuf_queue_core_init); + +/* Locking: Only usage in bttv unsafe find way to remove */ +int videobuf_queue_is_busy(struct videobuf_queue *q) +{ + int i; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + if (q->streaming) { + dprintk(1, "busy: streaming active\n"); + return 1; + } + if (q->reading) { + dprintk(1, "busy: pending read #1\n"); + return 1; + } + if (q->read_buf) { + dprintk(1, "busy: pending read #2\n"); + return 1; + } + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (q->bufs[i]->map) { + dprintk(1, "busy: buffer #%d mapped\n", i); + return 1; + } + if (q->bufs[i]->state == VIDEOBUF_QUEUED) { + dprintk(1, "busy: buffer #%d queued\n", i); + return 1; + } + if (q->bufs[i]->state == VIDEOBUF_ACTIVE) { + dprintk(1, "busy: buffer #%d avtive\n", i); + return 1; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(videobuf_queue_is_busy); + +/** + * __videobuf_free() - free all the buffers and their control structures + * + * This function can only be called if streaming/reading is off, i.e. no buffers + * are under control of the driver. + */ +/* Locking: Caller holds q->vb_lock */ +static int __videobuf_free(struct videobuf_queue *q) +{ + int i; + + dprintk(1, "%s\n", __func__); + if (!q) + return 0; + + if (q->streaming || q->reading) { + dprintk(1, "Cannot free buffers when streaming or reading\n"); + return -EBUSY; + } + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) + if (q->bufs[i] && q->bufs[i]->map) { + dprintk(1, "Cannot free mmapped buffers\n"); + return -EBUSY; + } + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + q->ops->buf_release(q, q->bufs[i]); + kfree(q->bufs[i]); + q->bufs[i] = NULL; + } + + return 0; +} + +/* Locking: Caller holds q->vb_lock */ +void videobuf_queue_cancel(struct videobuf_queue *q) +{ + unsigned long flags = 0; + int i; + + q->streaming = 0; + q->reading = 0; + wake_up_interruptible_sync(&q->wait); + + /* remove queued buffers from list */ + spin_lock_irqsave(q->irqlock, flags); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (q->bufs[i]->state == VIDEOBUF_QUEUED) { + list_del(&q->bufs[i]->queue); + q->bufs[i]->state = VIDEOBUF_ERROR; + wake_up_all(&q->bufs[i]->done); + } + } + spin_unlock_irqrestore(q->irqlock, flags); + + /* free all buffers + clear queue */ + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + q->ops->buf_release(q, q->bufs[i]); + } + INIT_LIST_HEAD(&q->stream); +} +EXPORT_SYMBOL_GPL(videobuf_queue_cancel); + +/* --------------------------------------------------------------------- */ + +/* Locking: Caller holds q->vb_lock */ +enum v4l2_field videobuf_next_field(struct videobuf_queue *q) +{ + enum v4l2_field field = q->field; + + BUG_ON(V4L2_FIELD_ANY == field); + + if (V4L2_FIELD_ALTERNATE == field) { + if (V4L2_FIELD_TOP == q->last) { + field = V4L2_FIELD_BOTTOM; + q->last = V4L2_FIELD_BOTTOM; + } else { + field = V4L2_FIELD_TOP; + q->last = V4L2_FIELD_TOP; + } + } + return field; +} +EXPORT_SYMBOL_GPL(videobuf_next_field); + +/* Locking: Caller holds q->vb_lock */ +static void videobuf_status(struct videobuf_queue *q, struct v4l2_buffer *b, + struct videobuf_buffer *vb, enum v4l2_buf_type type) +{ + MAGIC_CHECK(vb->magic, MAGIC_BUFFER); + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + b->index = vb->i; + b->type = type; + + b->memory = vb->memory; + switch (b->memory) { + case V4L2_MEMORY_MMAP: + b->m.offset = vb->boff; + b->length = vb->bsize; + break; + case V4L2_MEMORY_USERPTR: + b->m.userptr = vb->baddr; + b->length = vb->bsize; + break; + case V4L2_MEMORY_OVERLAY: + b->m.offset = vb->boff; + break; + } + + b->flags = 0; + if (vb->map) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + switch (vb->state) { + case VIDEOBUF_PREPARED: + case VIDEOBUF_QUEUED: + case VIDEOBUF_ACTIVE: + b->flags |= V4L2_BUF_FLAG_QUEUED; + break; + case VIDEOBUF_ERROR: + b->flags |= V4L2_BUF_FLAG_ERROR; + /* fall through */ + case VIDEOBUF_DONE: + b->flags |= V4L2_BUF_FLAG_DONE; + break; + case VIDEOBUF_NEEDS_INIT: + case VIDEOBUF_IDLE: + /* nothing */ + break; + } + + b->field = vb->field; + b->timestamp = vb->ts; + b->bytesused = vb->size; + b->sequence = vb->field_count >> 1; +} + +int videobuf_mmap_free(struct videobuf_queue *q) +{ + int ret; + videobuf_queue_lock(q); + ret = __videobuf_free(q); + videobuf_queue_unlock(q); + return ret; +} +EXPORT_SYMBOL_GPL(videobuf_mmap_free); + +/* Locking: Caller holds q->vb_lock */ +int __videobuf_mmap_setup(struct videobuf_queue *q, + unsigned int bcount, unsigned int bsize, + enum v4l2_memory memory) +{ + unsigned int i; + int err; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + err = __videobuf_free(q); + if (0 != err) + return err; + + /* Allocate and initialize buffers */ + for (i = 0; i < bcount; i++) { + q->bufs[i] = videobuf_alloc_vb(q); + + if (NULL == q->bufs[i]) + break; + + q->bufs[i]->i = i; + q->bufs[i]->memory = memory; + q->bufs[i]->bsize = bsize; + switch (memory) { + case V4L2_MEMORY_MMAP: + q->bufs[i]->boff = PAGE_ALIGN(bsize) * i; + break; + case V4L2_MEMORY_USERPTR: + case V4L2_MEMORY_OVERLAY: + /* nothing */ + break; + } + } + + if (!i) + return -ENOMEM; + + dprintk(1, "mmap setup: %d buffers, %d bytes each\n", i, bsize); + + return i; +} +EXPORT_SYMBOL_GPL(__videobuf_mmap_setup); + +int videobuf_mmap_setup(struct videobuf_queue *q, + unsigned int bcount, unsigned int bsize, + enum v4l2_memory memory) +{ + int ret; + videobuf_queue_lock(q); + ret = __videobuf_mmap_setup(q, bcount, bsize, memory); + videobuf_queue_unlock(q); + return ret; +} +EXPORT_SYMBOL_GPL(videobuf_mmap_setup); + +int videobuf_reqbufs(struct videobuf_queue *q, + struct v4l2_requestbuffers *req) +{ + unsigned int size, count; + int retval; + + if (req->count < 1) { + dprintk(1, "reqbufs: count invalid (%d)\n", req->count); + return -EINVAL; + } + + if (req->memory != V4L2_MEMORY_MMAP && + req->memory != V4L2_MEMORY_USERPTR && + req->memory != V4L2_MEMORY_OVERLAY) { + dprintk(1, "reqbufs: memory type invalid\n"); + return -EINVAL; + } + + videobuf_queue_lock(q); + if (req->type != q->type) { + dprintk(1, "reqbufs: queue type invalid\n"); + retval = -EINVAL; + goto done; + } + + if (q->streaming) { + dprintk(1, "reqbufs: streaming already exists\n"); + retval = -EBUSY; + goto done; + } + if (!list_empty(&q->stream)) { + dprintk(1, "reqbufs: stream running\n"); + retval = -EBUSY; + goto done; + } + + count = req->count; + if (count > VIDEO_MAX_FRAME) + count = VIDEO_MAX_FRAME; + size = 0; + q->ops->buf_setup(q, &count, &size); + dprintk(1, "reqbufs: bufs=%d, size=0x%x [%u pages total]\n", + count, size, + (unsigned int)((count * PAGE_ALIGN(size)) >> PAGE_SHIFT)); + + retval = __videobuf_mmap_setup(q, count, size, req->memory); + if (retval < 0) { + dprintk(1, "reqbufs: mmap setup returned %d\n", retval); + goto done; + } + + req->count = retval; + retval = 0; + + done: + videobuf_queue_unlock(q); + return retval; +} +EXPORT_SYMBOL_GPL(videobuf_reqbufs); + +int videobuf_querybuf(struct videobuf_queue *q, struct v4l2_buffer *b) +{ + int ret = -EINVAL; + + videobuf_queue_lock(q); + if (unlikely(b->type != q->type)) { + dprintk(1, "querybuf: Wrong type.\n"); + goto done; + } + if (unlikely(b->index >= VIDEO_MAX_FRAME)) { + dprintk(1, "querybuf: index out of range.\n"); + goto done; + } + if (unlikely(NULL == q->bufs[b->index])) { + dprintk(1, "querybuf: buffer is null.\n"); + goto done; + } + + videobuf_status(q, b, q->bufs[b->index], q->type); + + ret = 0; +done: + videobuf_queue_unlock(q); + return ret; +} +EXPORT_SYMBOL_GPL(videobuf_querybuf); + +int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b) +{ + struct videobuf_buffer *buf; + enum v4l2_field field; + unsigned long flags = 0; + int retval; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + if (b->memory == V4L2_MEMORY_MMAP) + down_read(¤t->mm->mmap_sem); + + videobuf_queue_lock(q); + retval = -EBUSY; + if (q->reading) { + dprintk(1, "qbuf: Reading running...\n"); + goto done; + } + retval = -EINVAL; + if (b->type != q->type) { + dprintk(1, "qbuf: Wrong type.\n"); + goto done; + } + if (b->index >= VIDEO_MAX_FRAME) { + dprintk(1, "qbuf: index out of range.\n"); + goto done; + } + buf = q->bufs[b->index]; + if (NULL == buf) { + dprintk(1, "qbuf: buffer is null.\n"); + goto done; + } + MAGIC_CHECK(buf->magic, MAGIC_BUFFER); + if (buf->memory != b->memory) { + dprintk(1, "qbuf: memory type is wrong.\n"); + goto done; + } + if (buf->state != VIDEOBUF_NEEDS_INIT && buf->state != VIDEOBUF_IDLE) { + dprintk(1, "qbuf: buffer is already queued or active.\n"); + goto done; + } + + switch (b->memory) { + case V4L2_MEMORY_MMAP: + if (0 == buf->baddr) { + dprintk(1, "qbuf: mmap requested " + "but buffer addr is zero!\n"); + goto done; + } + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT + || q->type == V4L2_BUF_TYPE_VBI_OUTPUT + || q->type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) { + buf->size = b->bytesused; + buf->field = b->field; + buf->ts = b->timestamp; + } + break; + case V4L2_MEMORY_USERPTR: + if (b->length < buf->bsize) { + dprintk(1, "qbuf: buffer length is not enough\n"); + goto done; + } + if (VIDEOBUF_NEEDS_INIT != buf->state && + buf->baddr != b->m.userptr) + q->ops->buf_release(q, buf); + buf->baddr = b->m.userptr; + break; + case V4L2_MEMORY_OVERLAY: + buf->boff = b->m.offset; + break; + default: + dprintk(1, "qbuf: wrong memory type\n"); + goto done; + } + + dprintk(1, "qbuf: requesting next field\n"); + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(q, buf, field); + if (0 != retval) { + dprintk(1, "qbuf: buffer_prepare returned %d\n", retval); + goto done; + } + + list_add_tail(&buf->stream, &q->stream); + if (q->streaming) { + spin_lock_irqsave(q->irqlock, flags); + q->ops->buf_queue(q, buf); + spin_unlock_irqrestore(q->irqlock, flags); + } + dprintk(1, "qbuf: succeeded\n"); + retval = 0; + wake_up_interruptible_sync(&q->wait); + +done: + videobuf_queue_unlock(q); + + if (b->memory == V4L2_MEMORY_MMAP) + up_read(¤t->mm->mmap_sem); + + return retval; +} +EXPORT_SYMBOL_GPL(videobuf_qbuf); + +/* Locking: Caller holds q->vb_lock */ +static int stream_next_buffer_check_queue(struct videobuf_queue *q, int noblock) +{ + int retval; + +checks: + if (!q->streaming) { + dprintk(1, "next_buffer: Not streaming\n"); + retval = -EINVAL; + goto done; + } + + if (list_empty(&q->stream)) { + if (noblock) { + retval = -EAGAIN; + dprintk(2, "next_buffer: no buffers to dequeue\n"); + goto done; + } else { + dprintk(2, "next_buffer: waiting on buffer\n"); + + /* Drop lock to avoid deadlock with qbuf */ + videobuf_queue_unlock(q); + + /* Checking list_empty and streaming is safe without + * locks because we goto checks to validate while + * holding locks before proceeding */ + retval = wait_event_interruptible(q->wait, + !list_empty(&q->stream) || !q->streaming); + videobuf_queue_lock(q); + + if (retval) + goto done; + + goto checks; + } + } + + retval = 0; + +done: + return retval; +} + +/* Locking: Caller holds q->vb_lock */ +static int stream_next_buffer(struct videobuf_queue *q, + struct videobuf_buffer **vb, int nonblocking) +{ + int retval; + struct videobuf_buffer *buf = NULL; + + retval = stream_next_buffer_check_queue(q, nonblocking); + if (retval) + goto done; + + buf = list_entry(q->stream.next, struct videobuf_buffer, stream); + retval = videobuf_waiton(q, buf, nonblocking, 1); + if (retval < 0) + goto done; + + *vb = buf; +done: + return retval; +} + +int videobuf_dqbuf(struct videobuf_queue *q, + struct v4l2_buffer *b, int nonblocking) +{ + struct videobuf_buffer *buf = NULL; + int retval; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + memset(b, 0, sizeof(*b)); + videobuf_queue_lock(q); + + retval = stream_next_buffer(q, &buf, nonblocking); + if (retval < 0) { + dprintk(1, "dqbuf: next_buffer error: %i\n", retval); + goto done; + } + + switch (buf->state) { + case VIDEOBUF_ERROR: + dprintk(1, "dqbuf: state is error\n"); + break; + case VIDEOBUF_DONE: + dprintk(1, "dqbuf: state is done\n"); + break; + default: + dprintk(1, "dqbuf: state invalid\n"); + retval = -EINVAL; + goto done; + } + CALL(q, sync, q, buf); + videobuf_status(q, b, buf, q->type); + list_del(&buf->stream); + buf->state = VIDEOBUF_IDLE; + b->flags &= ~V4L2_BUF_FLAG_DONE; +done: + videobuf_queue_unlock(q); + return retval; +} +EXPORT_SYMBOL_GPL(videobuf_dqbuf); + +int videobuf_streamon(struct videobuf_queue *q) +{ + struct videobuf_buffer *buf; + unsigned long flags = 0; + int retval; + + videobuf_queue_lock(q); + retval = -EBUSY; + if (q->reading) + goto done; + retval = 0; + if (q->streaming) + goto done; + q->streaming = 1; + spin_lock_irqsave(q->irqlock, flags); + list_for_each_entry(buf, &q->stream, stream) + if (buf->state == VIDEOBUF_PREPARED) + q->ops->buf_queue(q, buf); + spin_unlock_irqrestore(q->irqlock, flags); + + wake_up_interruptible_sync(&q->wait); +done: + videobuf_queue_unlock(q); + return retval; +} +EXPORT_SYMBOL_GPL(videobuf_streamon); + +/* Locking: Caller holds q->vb_lock */ +static int __videobuf_streamoff(struct videobuf_queue *q) +{ + if (!q->streaming) + return -EINVAL; + + videobuf_queue_cancel(q); + + return 0; +} + +int videobuf_streamoff(struct videobuf_queue *q) +{ + int retval; + + videobuf_queue_lock(q); + retval = __videobuf_streamoff(q); + videobuf_queue_unlock(q); + + return retval; +} +EXPORT_SYMBOL_GPL(videobuf_streamoff); + +/* Locking: Caller holds q->vb_lock */ +static ssize_t videobuf_read_zerocopy(struct videobuf_queue *q, + char __user *data, + size_t count, loff_t *ppos) +{ + enum v4l2_field field; + unsigned long flags = 0; + int retval; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + /* setup stuff */ + q->read_buf = videobuf_alloc_vb(q); + if (NULL == q->read_buf) + return -ENOMEM; + + q->read_buf->memory = V4L2_MEMORY_USERPTR; + q->read_buf->baddr = (unsigned long)data; + q->read_buf->bsize = count; + + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(q, q->read_buf, field); + if (0 != retval) + goto done; + + /* start capture & wait */ + spin_lock_irqsave(q->irqlock, flags); + q->ops->buf_queue(q, q->read_buf); + spin_unlock_irqrestore(q->irqlock, flags); + retval = videobuf_waiton(q, q->read_buf, 0, 0); + if (0 == retval) { + CALL(q, sync, q, q->read_buf); + if (VIDEOBUF_ERROR == q->read_buf->state) + retval = -EIO; + else + retval = q->read_buf->size; + } + +done: + /* cleanup */ + q->ops->buf_release(q, q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + return retval; +} + +static int __videobuf_copy_to_user(struct videobuf_queue *q, + struct videobuf_buffer *buf, + char __user *data, size_t count, + int nonblocking) +{ + void *vaddr = CALL(q, vaddr, buf); + + /* copy to userspace */ + if (count > buf->size - q->read_off) + count = buf->size - q->read_off; + + if (copy_to_user(data, vaddr + q->read_off, count)) + return -EFAULT; + + return count; +} + +static int __videobuf_copy_stream(struct videobuf_queue *q, + struct videobuf_buffer *buf, + char __user *data, size_t count, size_t pos, + int vbihack, int nonblocking) +{ + unsigned int *fc = CALL(q, vaddr, buf); + + if (vbihack) { + /* dirty, undocumented hack -- pass the frame counter + * within the last four bytes of each vbi data block. + * We need that one to maintain backward compatibility + * to all vbi decoding software out there ... */ + fc += (buf->size >> 2) - 1; + *fc = buf->field_count >> 1; + dprintk(1, "vbihack: %d\n", *fc); + } + + /* copy stuff using the common method */ + count = __videobuf_copy_to_user(q, buf, data, count, nonblocking); + + if ((count == -EFAULT) && (pos == 0)) + return -EFAULT; + + return count; +} + +ssize_t videobuf_read_one(struct videobuf_queue *q, + char __user *data, size_t count, loff_t *ppos, + int nonblocking) +{ + enum v4l2_field field; + unsigned long flags = 0; + unsigned size = 0, nbufs = 1; + int retval; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + videobuf_queue_lock(q); + + q->ops->buf_setup(q, &nbufs, &size); + + if (NULL == q->read_buf && + count >= size && + !nonblocking) { + retval = videobuf_read_zerocopy(q, data, count, ppos); + if (retval >= 0 || retval == -EIO) + /* ok, all done */ + goto done; + /* fallback to kernel bounce buffer on failures */ + } + + if (NULL == q->read_buf) { + /* need to capture a new frame */ + retval = -ENOMEM; + q->read_buf = videobuf_alloc_vb(q); + + dprintk(1, "video alloc=0x%p\n", q->read_buf); + if (NULL == q->read_buf) + goto done; + q->read_buf->memory = V4L2_MEMORY_USERPTR; + q->read_buf->bsize = count; /* preferred size */ + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(q, q->read_buf, field); + + if (0 != retval) { + kfree(q->read_buf); + q->read_buf = NULL; + goto done; + } + + spin_lock_irqsave(q->irqlock, flags); + q->ops->buf_queue(q, q->read_buf); + spin_unlock_irqrestore(q->irqlock, flags); + + q->read_off = 0; + } + + /* wait until capture is done */ + retval = videobuf_waiton(q, q->read_buf, nonblocking, 1); + if (0 != retval) + goto done; + + CALL(q, sync, q, q->read_buf); + + if (VIDEOBUF_ERROR == q->read_buf->state) { + /* catch I/O errors */ + q->ops->buf_release(q, q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + retval = -EIO; + goto done; + } + + /* Copy to userspace */ + retval = __videobuf_copy_to_user(q, q->read_buf, data, count, nonblocking); + if (retval < 0) + goto done; + + q->read_off += retval; + if (q->read_off == q->read_buf->size) { + /* all data copied, cleanup */ + q->ops->buf_release(q, q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + } + +done: + videobuf_queue_unlock(q); + return retval; +} +EXPORT_SYMBOL_GPL(videobuf_read_one); + +/* Locking: Caller holds q->vb_lock */ +static int __videobuf_read_start(struct videobuf_queue *q) +{ + enum v4l2_field field; + unsigned long flags = 0; + unsigned int count = 0, size = 0; + int err, i; + + q->ops->buf_setup(q, &count, &size); + if (count < 2) + count = 2; + if (count > VIDEO_MAX_FRAME) + count = VIDEO_MAX_FRAME; + size = PAGE_ALIGN(size); + + err = __videobuf_mmap_setup(q, count, size, V4L2_MEMORY_USERPTR); + if (err < 0) + return err; + + count = err; + + for (i = 0; i < count; i++) { + field = videobuf_next_field(q); + err = q->ops->buf_prepare(q, q->bufs[i], field); + if (err) + return err; + list_add_tail(&q->bufs[i]->stream, &q->stream); + } + spin_lock_irqsave(q->irqlock, flags); + for (i = 0; i < count; i++) + q->ops->buf_queue(q, q->bufs[i]); + spin_unlock_irqrestore(q->irqlock, flags); + q->reading = 1; + return 0; +} + +static void __videobuf_read_stop(struct videobuf_queue *q) +{ + int i; + + videobuf_queue_cancel(q); + __videobuf_free(q); + INIT_LIST_HEAD(&q->stream); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + kfree(q->bufs[i]); + q->bufs[i] = NULL; + } + q->read_buf = NULL; +} + +int videobuf_read_start(struct videobuf_queue *q) +{ + int rc; + + videobuf_queue_lock(q); + rc = __videobuf_read_start(q); + videobuf_queue_unlock(q); + + return rc; +} +EXPORT_SYMBOL_GPL(videobuf_read_start); + +void videobuf_read_stop(struct videobuf_queue *q) +{ + videobuf_queue_lock(q); + __videobuf_read_stop(q); + videobuf_queue_unlock(q); +} +EXPORT_SYMBOL_GPL(videobuf_read_stop); + +void videobuf_stop(struct videobuf_queue *q) +{ + videobuf_queue_lock(q); + + if (q->streaming) + __videobuf_streamoff(q); + + if (q->reading) + __videobuf_read_stop(q); + + videobuf_queue_unlock(q); +} +EXPORT_SYMBOL_GPL(videobuf_stop); + +ssize_t videobuf_read_stream(struct videobuf_queue *q, + char __user *data, size_t count, loff_t *ppos, + int vbihack, int nonblocking) +{ + int rc, retval; + unsigned long flags = 0; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + dprintk(2, "%s\n", __func__); + videobuf_queue_lock(q); + retval = -EBUSY; + if (q->streaming) + goto done; + if (!q->reading) { + retval = __videobuf_read_start(q); + if (retval < 0) + goto done; + } + + retval = 0; + while (count > 0) { + /* get / wait for data */ + if (NULL == q->read_buf) { + q->read_buf = list_entry(q->stream.next, + struct videobuf_buffer, + stream); + list_del(&q->read_buf->stream); + q->read_off = 0; + } + rc = videobuf_waiton(q, q->read_buf, nonblocking, 1); + if (rc < 0) { + if (0 == retval) + retval = rc; + break; + } + + if (q->read_buf->state == VIDEOBUF_DONE) { + rc = __videobuf_copy_stream(q, q->read_buf, data + retval, count, + retval, vbihack, nonblocking); + if (rc < 0) { + retval = rc; + break; + } + retval += rc; + count -= rc; + q->read_off += rc; + } else { + /* some error */ + q->read_off = q->read_buf->size; + if (0 == retval) + retval = -EIO; + } + + /* requeue buffer when done with copying */ + if (q->read_off == q->read_buf->size) { + list_add_tail(&q->read_buf->stream, + &q->stream); + spin_lock_irqsave(q->irqlock, flags); + q->ops->buf_queue(q, q->read_buf); + spin_unlock_irqrestore(q->irqlock, flags); + q->read_buf = NULL; + } + if (retval < 0) + break; + } + +done: + videobuf_queue_unlock(q); + return retval; +} +EXPORT_SYMBOL_GPL(videobuf_read_stream); + +unsigned int videobuf_poll_stream(struct file *file, + struct videobuf_queue *q, + poll_table *wait) +{ + unsigned long req_events = poll_requested_events(wait); + struct videobuf_buffer *buf = NULL; + unsigned int rc = 0; + + videobuf_queue_lock(q); + if (q->streaming) { + if (!list_empty(&q->stream)) + buf = list_entry(q->stream.next, + struct videobuf_buffer, stream); + } else if (req_events & (POLLIN | POLLRDNORM)) { + if (!q->reading) + __videobuf_read_start(q); + if (!q->reading) { + rc = POLLERR; + } else if (NULL == q->read_buf) { + q->read_buf = list_entry(q->stream.next, + struct videobuf_buffer, + stream); + list_del(&q->read_buf->stream); + q->read_off = 0; + } + buf = q->read_buf; + } + if (!buf) + rc = POLLERR; + + if (0 == rc) { + poll_wait(file, &buf->done, wait); + if (buf->state == VIDEOBUF_DONE || + buf->state == VIDEOBUF_ERROR) { + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + rc = POLLOUT | POLLWRNORM; + break; + default: + rc = POLLIN | POLLRDNORM; + break; + } + } + } + videobuf_queue_unlock(q); + return rc; +} +EXPORT_SYMBOL_GPL(videobuf_poll_stream); + +int videobuf_mmap_mapper(struct videobuf_queue *q, struct vm_area_struct *vma) +{ + int rc = -EINVAL; + int i; + + MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); + + if (!(vma->vm_flags & VM_WRITE) || !(vma->vm_flags & VM_SHARED)) { + dprintk(1, "mmap appl bug: PROT_WRITE and MAP_SHARED are required\n"); + return -EINVAL; + } + + videobuf_queue_lock(q); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + struct videobuf_buffer *buf = q->bufs[i]; + + if (buf && buf->memory == V4L2_MEMORY_MMAP && + buf->boff == (vma->vm_pgoff << PAGE_SHIFT)) { + rc = CALL(q, mmap_mapper, q, buf, vma); + break; + } + } + videobuf_queue_unlock(q); + + return rc; +} +EXPORT_SYMBOL_GPL(videobuf_mmap_mapper); diff --git a/drivers/media/v4l2-core/videobuf-dma-contig.c b/drivers/media/v4l2-core/videobuf-dma-contig.c new file mode 100644 index 000000000000..3a43ba0959bf --- /dev/null +++ b/drivers/media/v4l2-core/videobuf-dma-contig.c @@ -0,0 +1,510 @@ +/* + * helper functions for physically contiguous capture buffers + * + * The functions support hardware lacking scatter gather support + * (i.e. the buffers must be linear in physical memory) + * + * Copyright (c) 2008 Magnus Damm + * + * Based on videobuf-vmalloc.c, + * (c) 2007 Mauro Carvalho Chehab, <mchehab@infradead.org> + * + * 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 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/dma-mapping.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <media/videobuf-dma-contig.h> + +struct videobuf_dma_contig_memory { + u32 magic; + void *vaddr; + dma_addr_t dma_handle; + bool cached; + unsigned long size; +}; + +#define MAGIC_DC_MEM 0x0733ac61 +#define MAGIC_CHECK(is, should) \ + if (unlikely((is) != (should))) { \ + pr_err("magic mismatch: %x expected %x\n", (is), (should)); \ + BUG(); \ + } + +static int __videobuf_dc_alloc(struct device *dev, + struct videobuf_dma_contig_memory *mem, + unsigned long size, gfp_t flags) +{ + mem->size = size; + if (mem->cached) { + mem->vaddr = alloc_pages_exact(mem->size, flags | GFP_DMA); + if (mem->vaddr) { + int err; + + mem->dma_handle = dma_map_single(dev, mem->vaddr, + mem->size, + DMA_FROM_DEVICE); + err = dma_mapping_error(dev, mem->dma_handle); + if (err) { + dev_err(dev, "dma_map_single failed\n"); + + free_pages_exact(mem->vaddr, mem->size); + mem->vaddr = NULL; + return err; + } + } + } else + mem->vaddr = dma_alloc_coherent(dev, mem->size, + &mem->dma_handle, flags); + + if (!mem->vaddr) { + dev_err(dev, "memory alloc size %ld failed\n", mem->size); + return -ENOMEM; + } + + dev_dbg(dev, "dma mapped data is at %p (%ld)\n", mem->vaddr, mem->size); + + return 0; +} + +static void __videobuf_dc_free(struct device *dev, + struct videobuf_dma_contig_memory *mem) +{ + if (mem->cached) { + if (!mem->vaddr) + return; + dma_unmap_single(dev, mem->dma_handle, mem->size, + DMA_FROM_DEVICE); + free_pages_exact(mem->vaddr, mem->size); + } else + dma_free_coherent(dev, mem->size, mem->vaddr, mem->dma_handle); + + mem->vaddr = NULL; +} + +static void videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + dev_dbg(map->q->dev, "vm_open %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count++; +} + +static void videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + struct videobuf_queue *q = map->q; + int i; + + dev_dbg(q->dev, "vm_close %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count--; + if (0 == map->count) { + struct videobuf_dma_contig_memory *mem; + + dev_dbg(q->dev, "munmap %p q=%p\n", map, q); + videobuf_queue_lock(q); + + /* We need first to cancel streams, before unmapping */ + if (q->streaming) + videobuf_queue_cancel(q); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + + if (q->bufs[i]->map != map) + continue; + + mem = q->bufs[i]->priv; + if (mem) { + /* This callback is called only if kernel has + allocated memory and this memory is mmapped. + In this case, memory should be freed, + in order to do memory unmap. + */ + + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + /* vfree is not atomic - can't be + called with IRQ's disabled + */ + dev_dbg(q->dev, "buf[%d] freeing %p\n", + i, mem->vaddr); + + __videobuf_dc_free(q->dev, mem); + mem->vaddr = NULL; + } + + q->bufs[i]->map = NULL; + q->bufs[i]->baddr = 0; + } + + kfree(map); + + videobuf_queue_unlock(q); + } +} + +static const struct vm_operations_struct videobuf_vm_ops = { + .open = videobuf_vm_open, + .close = videobuf_vm_close, +}; + +/** + * videobuf_dma_contig_user_put() - reset pointer to user space buffer + * @mem: per-buffer private videobuf-dma-contig data + * + * This function resets the user space pointer + */ +static void videobuf_dma_contig_user_put(struct videobuf_dma_contig_memory *mem) +{ + mem->dma_handle = 0; + mem->size = 0; +} + +/** + * videobuf_dma_contig_user_get() - setup user space memory pointer + * @mem: per-buffer private videobuf-dma-contig data + * @vb: video buffer to map + * + * This function validates and sets up a pointer to user space memory. + * Only physically contiguous pfn-mapped memory is accepted. + * + * Returns 0 if successful. + */ +static int videobuf_dma_contig_user_get(struct videobuf_dma_contig_memory *mem, + struct videobuf_buffer *vb) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long prev_pfn, this_pfn; + unsigned long pages_done, user_address; + unsigned int offset; + int ret; + + offset = vb->baddr & ~PAGE_MASK; + mem->size = PAGE_ALIGN(vb->size + offset); + ret = -EINVAL; + + down_read(&mm->mmap_sem); + + vma = find_vma(mm, vb->baddr); + if (!vma) + goto out_up; + + if ((vb->baddr + mem->size) > vma->vm_end) + goto out_up; + + pages_done = 0; + prev_pfn = 0; /* kill warning */ + user_address = vb->baddr; + + while (pages_done < (mem->size >> PAGE_SHIFT)) { + ret = follow_pfn(vma, user_address, &this_pfn); + if (ret) + break; + + if (pages_done == 0) + mem->dma_handle = (this_pfn << PAGE_SHIFT) + offset; + else if (this_pfn != (prev_pfn + 1)) + ret = -EFAULT; + + if (ret) + break; + + prev_pfn = this_pfn; + user_address += PAGE_SIZE; + pages_done++; + } + +out_up: + up_read(¤t->mm->mmap_sem); + + return ret; +} + +static struct videobuf_buffer *__videobuf_alloc_vb(size_t size, bool cached) +{ + struct videobuf_dma_contig_memory *mem; + struct videobuf_buffer *vb; + + vb = kzalloc(size + sizeof(*mem), GFP_KERNEL); + if (vb) { + vb->priv = ((char *)vb) + size; + mem = vb->priv; + mem->magic = MAGIC_DC_MEM; + mem->cached = cached; + } + + return vb; +} + +static struct videobuf_buffer *__videobuf_alloc_uncached(size_t size) +{ + return __videobuf_alloc_vb(size, false); +} + +static struct videobuf_buffer *__videobuf_alloc_cached(size_t size) +{ + return __videobuf_alloc_vb(size, true); +} + +static void *__videobuf_to_vaddr(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->vaddr; +} + +static int __videobuf_iolock(struct videobuf_queue *q, + struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + struct videobuf_dma_contig_memory *mem = vb->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + dev_dbg(q->dev, "%s memory method MMAP\n", __func__); + + /* All handling should be done by __videobuf_mmap_mapper() */ + if (!mem->vaddr) { + dev_err(q->dev, "memory is not alloced/mmapped.\n"); + return -EINVAL; + } + break; + case V4L2_MEMORY_USERPTR: + dev_dbg(q->dev, "%s memory method USERPTR\n", __func__); + + /* handle pointer from user space */ + if (vb->baddr) + return videobuf_dma_contig_user_get(mem, vb); + + /* allocate memory for the read() method */ + if (__videobuf_dc_alloc(q->dev, mem, PAGE_ALIGN(vb->size), + GFP_KERNEL)) + return -ENOMEM; + break; + case V4L2_MEMORY_OVERLAY: + default: + dev_dbg(q->dev, "%s memory method OVERLAY/unknown\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int __videobuf_sync(struct videobuf_queue *q, + struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + dma_sync_single_for_cpu(q->dev, mem->dma_handle, mem->size, + DMA_FROM_DEVICE); + + return 0; +} + +static int __videobuf_mmap_mapper(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma) +{ + struct videobuf_dma_contig_memory *mem; + struct videobuf_mapping *map; + int retval; + unsigned long size; + unsigned long pos, start = vma->vm_start; + struct page *page; + + dev_dbg(q->dev, "%s\n", __func__); + + /* create mapping + update buffer list */ + map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); + if (!map) + return -ENOMEM; + + buf->map = map; + map->q = q; + + buf->baddr = vma->vm_start; + + mem = buf->priv; + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + if (__videobuf_dc_alloc(q->dev, mem, PAGE_ALIGN(buf->bsize), + GFP_KERNEL | __GFP_COMP)) + goto error; + + /* Try to remap memory */ + + size = vma->vm_end - vma->vm_start; + size = (size < mem->size) ? size : mem->size; + + if (!mem->cached) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + retval = remap_pfn_range(vma, vma->vm_start, + mem->dma_handle >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (retval) { + dev_err(q->dev, "mmap: remap failed with error %d. ", + retval); + dma_free_coherent(q->dev, mem->size, + mem->vaddr, mem->dma_handle); + goto error; + } + } else { + pos = (unsigned long)mem->vaddr; + + while (size > 0) { + page = virt_to_page((void *)pos); + if (NULL == page) { + dev_err(q->dev, "mmap: virt_to_page failed\n"); + __videobuf_dc_free(q->dev, mem); + goto error; + } + retval = vm_insert_page(vma, start, page); + if (retval) { + dev_err(q->dev, "mmap: insert failed with error %d\n", + retval); + __videobuf_dc_free(q->dev, mem); + goto error; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + } + + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + dev_dbg(q->dev, "mmap %p: q=%p %08lx-%08lx (%lx) pgoff %08lx buf %d\n", + map, q, vma->vm_start, vma->vm_end, + (long int)buf->bsize, vma->vm_pgoff, buf->i); + + videobuf_vm_open(vma); + + return 0; + +error: + kfree(map); + return -ENOMEM; +} + +static struct videobuf_qtype_ops qops = { + .magic = MAGIC_QTYPE_OPS, + .alloc_vb = __videobuf_alloc_uncached, + .iolock = __videobuf_iolock, + .mmap_mapper = __videobuf_mmap_mapper, + .vaddr = __videobuf_to_vaddr, +}; + +static struct videobuf_qtype_ops qops_cached = { + .magic = MAGIC_QTYPE_OPS, + .alloc_vb = __videobuf_alloc_cached, + .iolock = __videobuf_iolock, + .sync = __videobuf_sync, + .mmap_mapper = __videobuf_mmap_mapper, + .vaddr = __videobuf_to_vaddr, +}; + +void videobuf_queue_dma_contig_init(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct mutex *ext_lock) +{ + videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize, + priv, &qops, ext_lock); +} +EXPORT_SYMBOL_GPL(videobuf_queue_dma_contig_init); + +void videobuf_queue_dma_contig_init_cached(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, struct mutex *ext_lock) +{ + videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize, + priv, &qops_cached, ext_lock); +} +EXPORT_SYMBOL_GPL(videobuf_queue_dma_contig_init_cached); + +dma_addr_t videobuf_to_dma_contig(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->dma_handle; +} +EXPORT_SYMBOL_GPL(videobuf_to_dma_contig); + +void videobuf_dma_contig_free(struct videobuf_queue *q, + struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + /* mmapped memory can't be freed here, otherwise mmapped region + would be released, while still needed. In this case, the memory + release should happen inside videobuf_vm_close(). + So, it should free memory only if the memory were allocated for + read() operation. + */ + if (buf->memory != V4L2_MEMORY_USERPTR) + return; + + if (!mem) + return; + + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + /* handle user space pointer case */ + if (buf->baddr) { + videobuf_dma_contig_user_put(mem); + return; + } + + /* read() method */ + if (mem->vaddr) { + __videobuf_dc_free(q->dev, mem); + mem->vaddr = NULL; + } +} +EXPORT_SYMBOL_GPL(videobuf_dma_contig_free); + +MODULE_DESCRIPTION("helper module to manage video4linux dma contig buffers"); +MODULE_AUTHOR("Magnus Damm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/videobuf-dma-sg.c b/drivers/media/v4l2-core/videobuf-dma-sg.c new file mode 100644 index 000000000000..f300deafd268 --- /dev/null +++ b/drivers/media/v4l2-core/videobuf-dma-sg.c @@ -0,0 +1,633 @@ +/* + * helper functions for SG DMA video4linux capture buffers + * + * The functions expect the hardware being able to scatter gather + * (i.e. the buffers are not linear in physical memory, but fragmented + * into PAGE_SIZE chunks). They also assume the driver does not need + * to touch the video data. + * + * (c) 2007 Mauro Carvalho Chehab, <mchehab@infradead.org> + * + * Highly based on video-buf written originally by: + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> + * (c) 2006 Mauro Carvalho Chehab, <mchehab@infradead.org> + * (c) 2006 Ted Walther and John Sokol + * + * 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 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/scatterlist.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include <media/videobuf-dma-sg.h> + +#define MAGIC_DMABUF 0x19721112 +#define MAGIC_SG_MEM 0x17890714 + +#define MAGIC_CHECK(is, should) \ + if (unlikely((is) != (should))) { \ + printk(KERN_ERR "magic mismatch: %x (expected %x)\n", \ + is, should); \ + BUG(); \ + } + +static int debug; +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("helper module to manage video4linux dma sg buffers"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>"); +MODULE_LICENSE("GPL"); + +#define dprintk(level, fmt, arg...) \ + if (debug >= level) \ + printk(KERN_DEBUG "vbuf-sg: " fmt , ## arg) + +/* --------------------------------------------------------------------- */ + +/* + * Return a scatterlist for some page-aligned vmalloc()'ed memory + * block (NULL on errors). Memory for the scatterlist is allocated + * using kmalloc. The caller must free the memory. + */ +static struct scatterlist *videobuf_vmalloc_to_sg(unsigned char *virt, + int nr_pages) +{ + struct scatterlist *sglist; + struct page *pg; + int i; + + sglist = vzalloc(nr_pages * sizeof(*sglist)); + if (NULL == sglist) + return NULL; + sg_init_table(sglist, nr_pages); + for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { + pg = vmalloc_to_page(virt); + if (NULL == pg) + goto err; + BUG_ON(PageHighMem(pg)); + sg_set_page(&sglist[i], pg, PAGE_SIZE, 0); + } + return sglist; + +err: + vfree(sglist); + return NULL; +} + +/* + * Return a scatterlist for a an array of userpages (NULL on errors). + * Memory for the scatterlist is allocated using kmalloc. The caller + * must free the memory. + */ +static struct scatterlist *videobuf_pages_to_sg(struct page **pages, + int nr_pages, int offset, size_t size) +{ + struct scatterlist *sglist; + int i; + + if (NULL == pages[0]) + return NULL; + sglist = vmalloc(nr_pages * sizeof(*sglist)); + if (NULL == sglist) + return NULL; + sg_init_table(sglist, nr_pages); + + if (PageHighMem(pages[0])) + /* DMA to highmem pages might not work */ + goto highmem; + sg_set_page(&sglist[0], pages[0], + min_t(size_t, PAGE_SIZE - offset, size), offset); + size -= min_t(size_t, PAGE_SIZE - offset, size); + for (i = 1; i < nr_pages; i++) { + if (NULL == pages[i]) + goto nopage; + if (PageHighMem(pages[i])) + goto highmem; + sg_set_page(&sglist[i], pages[i], min_t(size_t, PAGE_SIZE, size), 0); + size -= min_t(size_t, PAGE_SIZE, size); + } + return sglist; + +nopage: + dprintk(2, "sgl: oops - no page\n"); + vfree(sglist); + return NULL; + +highmem: + dprintk(2, "sgl: oops - highmem page\n"); + vfree(sglist); + return NULL; +} + +/* --------------------------------------------------------------------- */ + +struct videobuf_dmabuf *videobuf_to_dma(struct videobuf_buffer *buf) +{ + struct videobuf_dma_sg_memory *mem = buf->priv; + BUG_ON(!mem); + + MAGIC_CHECK(mem->magic, MAGIC_SG_MEM); + + return &mem->dma; +} +EXPORT_SYMBOL_GPL(videobuf_to_dma); + +void videobuf_dma_init(struct videobuf_dmabuf *dma) +{ + memset(dma, 0, sizeof(*dma)); + dma->magic = MAGIC_DMABUF; +} +EXPORT_SYMBOL_GPL(videobuf_dma_init); + +static int videobuf_dma_init_user_locked(struct videobuf_dmabuf *dma, + int direction, unsigned long data, unsigned long size) +{ + unsigned long first, last; + int err, rw = 0; + + dma->direction = direction; + switch (dma->direction) { + case DMA_FROM_DEVICE: + rw = READ; + break; + case DMA_TO_DEVICE: + rw = WRITE; + break; + default: + BUG(); + } + + first = (data & PAGE_MASK) >> PAGE_SHIFT; + last = ((data+size-1) & PAGE_MASK) >> PAGE_SHIFT; + dma->offset = data & ~PAGE_MASK; + dma->size = size; + dma->nr_pages = last-first+1; + dma->pages = kmalloc(dma->nr_pages * sizeof(struct page *), GFP_KERNEL); + if (NULL == dma->pages) + return -ENOMEM; + + dprintk(1, "init user [0x%lx+0x%lx => %d pages]\n", + data, size, dma->nr_pages); + + err = get_user_pages(current, current->mm, + data & PAGE_MASK, dma->nr_pages, + rw == READ, 1, /* force */ + dma->pages, NULL); + + if (err != dma->nr_pages) { + dma->nr_pages = (err >= 0) ? err : 0; + dprintk(1, "get_user_pages: err=%d [%d]\n", err, dma->nr_pages); + return err < 0 ? err : -EINVAL; + } + return 0; +} + +int videobuf_dma_init_user(struct videobuf_dmabuf *dma, int direction, + unsigned long data, unsigned long size) +{ + int ret; + + down_read(¤t->mm->mmap_sem); + ret = videobuf_dma_init_user_locked(dma, direction, data, size); + up_read(¤t->mm->mmap_sem); + + return ret; +} +EXPORT_SYMBOL_GPL(videobuf_dma_init_user); + +int videobuf_dma_init_kernel(struct videobuf_dmabuf *dma, int direction, + int nr_pages) +{ + dprintk(1, "init kernel [%d pages]\n", nr_pages); + + dma->direction = direction; + dma->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT); + if (NULL == dma->vaddr) { + dprintk(1, "vmalloc_32(%d pages) failed\n", nr_pages); + return -ENOMEM; + } + + dprintk(1, "vmalloc is at addr 0x%08lx, size=%d\n", + (unsigned long)dma->vaddr, + nr_pages << PAGE_SHIFT); + + memset(dma->vaddr, 0, nr_pages << PAGE_SHIFT); + dma->nr_pages = nr_pages; + + return 0; +} +EXPORT_SYMBOL_GPL(videobuf_dma_init_kernel); + +int videobuf_dma_init_overlay(struct videobuf_dmabuf *dma, int direction, + dma_addr_t addr, int nr_pages) +{ + dprintk(1, "init overlay [%d pages @ bus 0x%lx]\n", + nr_pages, (unsigned long)addr); + dma->direction = direction; + + if (0 == addr) + return -EINVAL; + + dma->bus_addr = addr; + dma->nr_pages = nr_pages; + + return 0; +} +EXPORT_SYMBOL_GPL(videobuf_dma_init_overlay); + +int videobuf_dma_map(struct device *dev, struct videobuf_dmabuf *dma) +{ + MAGIC_CHECK(dma->magic, MAGIC_DMABUF); + BUG_ON(0 == dma->nr_pages); + + if (dma->pages) { + dma->sglist = videobuf_pages_to_sg(dma->pages, dma->nr_pages, + dma->offset, dma->size); + } + if (dma->vaddr) { + dma->sglist = videobuf_vmalloc_to_sg(dma->vaddr, + dma->nr_pages); + } + if (dma->bus_addr) { + dma->sglist = vmalloc(sizeof(*dma->sglist)); + if (NULL != dma->sglist) { + dma->sglen = 1; + sg_dma_address(&dma->sglist[0]) = dma->bus_addr + & PAGE_MASK; + dma->sglist[0].offset = dma->bus_addr & ~PAGE_MASK; + sg_dma_len(&dma->sglist[0]) = dma->nr_pages * PAGE_SIZE; + } + } + if (NULL == dma->sglist) { + dprintk(1, "scatterlist is NULL\n"); + return -ENOMEM; + } + if (!dma->bus_addr) { + dma->sglen = dma_map_sg(dev, dma->sglist, + dma->nr_pages, dma->direction); + if (0 == dma->sglen) { + printk(KERN_WARNING + "%s: videobuf_map_sg failed\n", __func__); + vfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + return -ENOMEM; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(videobuf_dma_map); + +int videobuf_dma_unmap(struct device *dev, struct videobuf_dmabuf *dma) +{ + MAGIC_CHECK(dma->magic, MAGIC_DMABUF); + + if (!dma->sglen) + return 0; + + dma_unmap_sg(dev, dma->sglist, dma->sglen, dma->direction); + + vfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(videobuf_dma_unmap); + +int videobuf_dma_free(struct videobuf_dmabuf *dma) +{ + int i; + MAGIC_CHECK(dma->magic, MAGIC_DMABUF); + BUG_ON(dma->sglen); + + if (dma->pages) { + for (i = 0; i < dma->nr_pages; i++) + page_cache_release(dma->pages[i]); + kfree(dma->pages); + dma->pages = NULL; + } + + vfree(dma->vaddr); + dma->vaddr = NULL; + + if (dma->bus_addr) + dma->bus_addr = 0; + dma->direction = DMA_NONE; + + return 0; +} +EXPORT_SYMBOL_GPL(videobuf_dma_free); + +/* --------------------------------------------------------------------- */ + +static void videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + dprintk(2, "vm_open %p [count=%d,vma=%08lx-%08lx]\n", map, + map->count, vma->vm_start, vma->vm_end); + + map->count++; +} + +static void videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + struct videobuf_queue *q = map->q; + struct videobuf_dma_sg_memory *mem; + int i; + + dprintk(2, "vm_close %p [count=%d,vma=%08lx-%08lx]\n", map, + map->count, vma->vm_start, vma->vm_end); + + map->count--; + if (0 == map->count) { + dprintk(1, "munmap %p q=%p\n", map, q); + videobuf_queue_lock(q); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + mem = q->bufs[i]->priv; + if (!mem) + continue; + + MAGIC_CHECK(mem->magic, MAGIC_SG_MEM); + + if (q->bufs[i]->map != map) + continue; + q->bufs[i]->map = NULL; + q->bufs[i]->baddr = 0; + q->ops->buf_release(q, q->bufs[i]); + } + videobuf_queue_unlock(q); + kfree(map); + } + return; +} + +/* + * Get a anonymous page for the mapping. Make sure we can DMA to that + * memory location with 32bit PCI devices (i.e. don't use highmem for + * now ...). Bounce buffers don't work very well for the data rates + * video capture has. + */ +static int videobuf_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct page *page; + + dprintk(3, "fault: fault @ %08lx [vma %08lx-%08lx]\n", + (unsigned long)vmf->virtual_address, + vma->vm_start, vma->vm_end); + + page = alloc_page(GFP_USER | __GFP_DMA32); + if (!page) + return VM_FAULT_OOM; + clear_user_highpage(page, (unsigned long)vmf->virtual_address); + vmf->page = page; + + return 0; +} + +static const struct vm_operations_struct videobuf_vm_ops = { + .open = videobuf_vm_open, + .close = videobuf_vm_close, + .fault = videobuf_vm_fault, +}; + +/* --------------------------------------------------------------------- + * SG handlers for the generic methods + */ + +/* Allocated area consists on 3 parts: + struct video_buffer + struct <driver>_buffer (cx88_buffer, saa7134_buf, ...) + struct videobuf_dma_sg_memory + */ + +static struct videobuf_buffer *__videobuf_alloc_vb(size_t size) +{ + struct videobuf_dma_sg_memory *mem; + struct videobuf_buffer *vb; + + vb = kzalloc(size + sizeof(*mem), GFP_KERNEL); + if (!vb) + return vb; + + mem = vb->priv = ((char *)vb) + size; + mem->magic = MAGIC_SG_MEM; + + videobuf_dma_init(&mem->dma); + + dprintk(1, "%s: allocated at %p(%ld+%ld) & %p(%ld)\n", + __func__, vb, (long)sizeof(*vb), (long)size - sizeof(*vb), + mem, (long)sizeof(*mem)); + + return vb; +} + +static void *__videobuf_to_vaddr(struct videobuf_buffer *buf) +{ + struct videobuf_dma_sg_memory *mem = buf->priv; + BUG_ON(!mem); + + MAGIC_CHECK(mem->magic, MAGIC_SG_MEM); + + return mem->dma.vaddr; +} + +static int __videobuf_iolock(struct videobuf_queue *q, + struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + int err, pages; + dma_addr_t bus; + struct videobuf_dma_sg_memory *mem = vb->priv; + BUG_ON(!mem); + + MAGIC_CHECK(mem->magic, MAGIC_SG_MEM); + + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + case V4L2_MEMORY_USERPTR: + if (0 == vb->baddr) { + /* no userspace addr -- kernel bounce buffer */ + pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT; + err = videobuf_dma_init_kernel(&mem->dma, + DMA_FROM_DEVICE, + pages); + if (0 != err) + return err; + } else if (vb->memory == V4L2_MEMORY_USERPTR) { + /* dma directly to userspace */ + err = videobuf_dma_init_user(&mem->dma, + DMA_FROM_DEVICE, + vb->baddr, vb->bsize); + if (0 != err) + return err; + } else { + /* NOTE: HACK: videobuf_iolock on V4L2_MEMORY_MMAP + buffers can only be called from videobuf_qbuf + we take current->mm->mmap_sem there, to prevent + locking inversion, so don't take it here */ + + err = videobuf_dma_init_user_locked(&mem->dma, + DMA_FROM_DEVICE, + vb->baddr, vb->bsize); + if (0 != err) + return err; + } + break; + case V4L2_MEMORY_OVERLAY: + if (NULL == fbuf) + return -EINVAL; + /* FIXME: need sanity checks for vb->boff */ + /* + * Using a double cast to avoid compiler warnings when + * building for PAE. Compiler doesn't like direct casting + * of a 32 bit ptr to 64 bit integer. + */ + bus = (dma_addr_t)(unsigned long)fbuf->base + vb->boff; + pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT; + err = videobuf_dma_init_overlay(&mem->dma, DMA_FROM_DEVICE, + bus, pages); + if (0 != err) + return err; + break; + default: + BUG(); + } + err = videobuf_dma_map(q->dev, &mem->dma); + if (0 != err) + return err; + + return 0; +} + +static int __videobuf_sync(struct videobuf_queue *q, + struct videobuf_buffer *buf) +{ + struct videobuf_dma_sg_memory *mem = buf->priv; + BUG_ON(!mem || !mem->dma.sglen); + + MAGIC_CHECK(mem->magic, MAGIC_SG_MEM); + MAGIC_CHECK(mem->dma.magic, MAGIC_DMABUF); + + dma_sync_sg_for_cpu(q->dev, mem->dma.sglist, + mem->dma.sglen, mem->dma.direction); + + return 0; +} + +static int __videobuf_mmap_mapper(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma) +{ + struct videobuf_dma_sg_memory *mem = buf->priv; + struct videobuf_mapping *map; + unsigned int first, last, size = 0, i; + int retval; + + retval = -EINVAL; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_SG_MEM); + + /* look for first buffer to map */ + for (first = 0; first < VIDEO_MAX_FRAME; first++) { + if (buf == q->bufs[first]) { + size = PAGE_ALIGN(q->bufs[first]->bsize); + break; + } + } + + /* paranoia, should never happen since buf is always valid. */ + if (!size) { + dprintk(1, "mmap app bug: offset invalid [offset=0x%lx]\n", + (vma->vm_pgoff << PAGE_SHIFT)); + goto done; + } + + last = first; + + /* create mapping + update buffer list */ + retval = -ENOMEM; + map = kmalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); + if (NULL == map) + goto done; + + size = 0; + for (i = first; i <= last; i++) { + if (NULL == q->bufs[i]) + continue; + q->bufs[i]->map = map; + q->bufs[i]->baddr = vma->vm_start + size; + size += PAGE_ALIGN(q->bufs[i]->bsize); + } + + map->count = 1; + map->q = q; + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED; + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + vma->vm_private_data = map; + dprintk(1, "mmap %p: q=%p %08lx-%08lx pgoff %08lx bufs %d-%d\n", + map, q, vma->vm_start, vma->vm_end, vma->vm_pgoff, first, last); + retval = 0; + +done: + return retval; +} + +static struct videobuf_qtype_ops sg_ops = { + .magic = MAGIC_QTYPE_OPS, + + .alloc_vb = __videobuf_alloc_vb, + .iolock = __videobuf_iolock, + .sync = __videobuf_sync, + .mmap_mapper = __videobuf_mmap_mapper, + .vaddr = __videobuf_to_vaddr, +}; + +void *videobuf_sg_alloc(size_t size) +{ + struct videobuf_queue q; + + /* Required to make generic handler to call __videobuf_alloc */ + q.int_ops = &sg_ops; + + q.msize = size; + + return videobuf_alloc_vb(&q); +} +EXPORT_SYMBOL_GPL(videobuf_sg_alloc); + +void videobuf_queue_sg_init(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct mutex *ext_lock) +{ + videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize, + priv, &sg_ops, ext_lock); +} +EXPORT_SYMBOL_GPL(videobuf_queue_sg_init); + diff --git a/drivers/media/v4l2-core/videobuf-dvb.c b/drivers/media/v4l2-core/videobuf-dvb.c new file mode 100644 index 000000000000..b7efa4516d36 --- /dev/null +++ b/drivers/media/v4l2-core/videobuf-dvb.c @@ -0,0 +1,398 @@ +/* + * + * some helper function for simple DVB cards which simply DMA the + * complete transport stream and let the computer sort everything else + * (i.e. we are using the software demux, ...). Also uses the + * video-buf to manage DMA buffers. + * + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs] + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kthread.h> +#include <linux/file.h> +#include <linux/slab.h> + +#include <linux/freezer.h> + +#include <media/videobuf-core.h> +#include <media/videobuf-dvb.h> + +/* ------------------------------------------------------------------ */ + +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages"); + +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG "%s/dvb: " fmt, dvb->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int videobuf_dvb_thread(void *data) +{ + struct videobuf_dvb *dvb = data; + struct videobuf_buffer *buf; + unsigned long flags; + void *outp; + + dprintk("dvb thread started\n"); + set_freezable(); + videobuf_read_start(&dvb->dvbq); + + for (;;) { + /* fetch next buffer */ + buf = list_entry(dvb->dvbq.stream.next, + struct videobuf_buffer, stream); + list_del(&buf->stream); + videobuf_waiton(&dvb->dvbq, buf, 0, 1); + + /* no more feeds left or stop_feed() asked us to quit */ + if (0 == dvb->nfeeds) + break; + if (kthread_should_stop()) + break; + try_to_freeze(); + + /* feed buffer data to demux */ + outp = videobuf_queue_to_vaddr(&dvb->dvbq, buf); + + if (buf->state == VIDEOBUF_DONE) + dvb_dmx_swfilter(&dvb->demux, outp, + buf->size); + + /* requeue buffer */ + list_add_tail(&buf->stream,&dvb->dvbq.stream); + spin_lock_irqsave(dvb->dvbq.irqlock,flags); + dvb->dvbq.ops->buf_queue(&dvb->dvbq,buf); + spin_unlock_irqrestore(dvb->dvbq.irqlock,flags); + } + + videobuf_read_stop(&dvb->dvbq); + dprintk("dvb thread stopped\n"); + + /* Hmm, linux becomes *very* unhappy without this ... */ + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + return 0; +} + +static int videobuf_dvb_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct videobuf_dvb *dvb = demux->priv; + int rc; + + if (!demux->dmx.frontend) + return -EINVAL; + + mutex_lock(&dvb->lock); + dvb->nfeeds++; + rc = dvb->nfeeds; + + if (NULL != dvb->thread) + goto out; + dvb->thread = kthread_run(videobuf_dvb_thread, + dvb, "%s dvb", dvb->name); + if (IS_ERR(dvb->thread)) { + rc = PTR_ERR(dvb->thread); + dvb->thread = NULL; + } + +out: + mutex_unlock(&dvb->lock); + return rc; +} + +static int videobuf_dvb_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct videobuf_dvb *dvb = demux->priv; + int err = 0; + + mutex_lock(&dvb->lock); + dvb->nfeeds--; + if (0 == dvb->nfeeds && NULL != dvb->thread) { + err = kthread_stop(dvb->thread); + dvb->thread = NULL; + } + mutex_unlock(&dvb->lock); + return err; +} + +static int videobuf_dvb_register_adapter(struct videobuf_dvb_frontends *fe, + struct module *module, + void *adapter_priv, + struct device *device, + char *adapter_name, + short *adapter_nr, + int mfe_shared) +{ + int result; + + mutex_init(&fe->lock); + + /* register adapter */ + result = dvb_register_adapter(&fe->adapter, adapter_name, module, + device, adapter_nr); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_register_adapter failed (errno = %d)\n", + adapter_name, result); + } + fe->adapter.priv = adapter_priv; + fe->adapter.mfe_shared = mfe_shared; + + return result; +} + +static int videobuf_dvb_register_frontend(struct dvb_adapter *adapter, + struct videobuf_dvb *dvb) +{ + int result; + + /* register frontend */ + result = dvb_register_frontend(adapter, dvb->frontend); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_register_frontend failed (errno = %d)\n", + dvb->name, result); + goto fail_frontend; + } + + /* register demux stuff */ + dvb->demux.dmx.capabilities = + DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + dvb->demux.priv = dvb; + dvb->demux.filternum = 256; + dvb->demux.feednum = 256; + dvb->demux.start_feed = videobuf_dvb_start_feed; + dvb->demux.stop_feed = videobuf_dvb_stop_feed; + result = dvb_dmx_init(&dvb->demux); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_dmx_init failed (errno = %d)\n", + dvb->name, result); + goto fail_dmx; + } + + dvb->dmxdev.filternum = 256; + dvb->dmxdev.demux = &dvb->demux.dmx; + dvb->dmxdev.capabilities = 0; + result = dvb_dmxdev_init(&dvb->dmxdev, adapter); + + if (result < 0) { + printk(KERN_WARNING "%s: dvb_dmxdev_init failed (errno = %d)\n", + dvb->name, result); + goto fail_dmxdev; + } + + dvb->fe_hw.source = DMX_FRONTEND_0; + result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw); + if (result < 0) { + printk(KERN_WARNING "%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n", + dvb->name, result); + goto fail_fe_hw; + } + + dvb->fe_mem.source = DMX_MEMORY_FE; + result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem); + if (result < 0) { + printk(KERN_WARNING "%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n", + dvb->name, result); + goto fail_fe_mem; + } + + result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw); + if (result < 0) { + printk(KERN_WARNING "%s: connect_frontend failed (errno = %d)\n", + dvb->name, result); + goto fail_fe_conn; + } + + /* register network adapter */ + result = dvb_net_init(adapter, &dvb->net, &dvb->demux.dmx); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_net_init failed (errno = %d)\n", + dvb->name, result); + goto fail_fe_conn; + } + return 0; + +fail_fe_conn: + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); +fail_fe_mem: + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); +fail_fe_hw: + dvb_dmxdev_release(&dvb->dmxdev); +fail_dmxdev: + dvb_dmx_release(&dvb->demux); +fail_dmx: + dvb_unregister_frontend(dvb->frontend); +fail_frontend: + dvb_frontend_detach(dvb->frontend); + dvb->frontend = NULL; + + return result; +} + +/* ------------------------------------------------------------------ */ +/* Register a single adapter and one or more frontends */ +int videobuf_dvb_register_bus(struct videobuf_dvb_frontends *f, + struct module *module, + void *adapter_priv, + struct device *device, + short *adapter_nr, + int mfe_shared) +{ + struct list_head *list, *q; + struct videobuf_dvb_frontend *fe; + int res; + + fe = videobuf_dvb_get_frontend(f, 1); + if (!fe) { + printk(KERN_WARNING "Unable to register the adapter which has no frontends\n"); + return -EINVAL; + } + + /* Bring up the adapter */ + res = videobuf_dvb_register_adapter(f, module, adapter_priv, device, + fe->dvb.name, adapter_nr, mfe_shared); + if (res < 0) { + printk(KERN_WARNING "videobuf_dvb_register_adapter failed (errno = %d)\n", res); + return res; + } + + /* Attach all of the frontends to the adapter */ + mutex_lock(&f->lock); + list_for_each_safe(list, q, &f->felist) { + fe = list_entry(list, struct videobuf_dvb_frontend, felist); + res = videobuf_dvb_register_frontend(&f->adapter, &fe->dvb); + if (res < 0) { + printk(KERN_WARNING "%s: videobuf_dvb_register_frontend failed (errno = %d)\n", + fe->dvb.name, res); + goto err; + } + } + mutex_unlock(&f->lock); + return 0; + +err: + mutex_unlock(&f->lock); + videobuf_dvb_unregister_bus(f); + return res; +} +EXPORT_SYMBOL(videobuf_dvb_register_bus); + +void videobuf_dvb_unregister_bus(struct videobuf_dvb_frontends *f) +{ + videobuf_dvb_dealloc_frontends(f); + + dvb_unregister_adapter(&f->adapter); +} +EXPORT_SYMBOL(videobuf_dvb_unregister_bus); + +struct videobuf_dvb_frontend *videobuf_dvb_get_frontend( + struct videobuf_dvb_frontends *f, int id) +{ + struct list_head *list, *q; + struct videobuf_dvb_frontend *fe, *ret = NULL; + + mutex_lock(&f->lock); + + list_for_each_safe(list, q, &f->felist) { + fe = list_entry(list, struct videobuf_dvb_frontend, felist); + if (fe->id == id) { + ret = fe; + break; + } + } + + mutex_unlock(&f->lock); + + return ret; +} +EXPORT_SYMBOL(videobuf_dvb_get_frontend); + +int videobuf_dvb_find_frontend(struct videobuf_dvb_frontends *f, + struct dvb_frontend *p) +{ + struct list_head *list, *q; + struct videobuf_dvb_frontend *fe = NULL; + int ret = 0; + + mutex_lock(&f->lock); + + list_for_each_safe(list, q, &f->felist) { + fe = list_entry(list, struct videobuf_dvb_frontend, felist); + if (fe->dvb.frontend == p) { + ret = fe->id; + break; + } + } + + mutex_unlock(&f->lock); + + return ret; +} +EXPORT_SYMBOL(videobuf_dvb_find_frontend); + +struct videobuf_dvb_frontend *videobuf_dvb_alloc_frontend( + struct videobuf_dvb_frontends *f, int id) +{ + struct videobuf_dvb_frontend *fe; + + fe = kzalloc(sizeof(struct videobuf_dvb_frontend), GFP_KERNEL); + if (fe == NULL) + goto fail_alloc; + + fe->id = id; + mutex_init(&fe->dvb.lock); + + mutex_lock(&f->lock); + list_add_tail(&fe->felist, &f->felist); + mutex_unlock(&f->lock); + +fail_alloc: + return fe; +} +EXPORT_SYMBOL(videobuf_dvb_alloc_frontend); + +void videobuf_dvb_dealloc_frontends(struct videobuf_dvb_frontends *f) +{ + struct list_head *list, *q; + struct videobuf_dvb_frontend *fe; + + mutex_lock(&f->lock); + list_for_each_safe(list, q, &f->felist) { + fe = list_entry(list, struct videobuf_dvb_frontend, felist); + if (fe->dvb.net.dvbdev) { + dvb_net_release(&fe->dvb.net); + fe->dvb.demux.dmx.remove_frontend(&fe->dvb.demux.dmx, + &fe->dvb.fe_mem); + fe->dvb.demux.dmx.remove_frontend(&fe->dvb.demux.dmx, + &fe->dvb.fe_hw); + dvb_dmxdev_release(&fe->dvb.dmxdev); + dvb_dmx_release(&fe->dvb.demux); + dvb_unregister_frontend(fe->dvb.frontend); + } + if (fe->dvb.frontend) + /* always allocated, may have been reset */ + dvb_frontend_detach(fe->dvb.frontend); + list_del(list); /* remove list entry */ + kfree(fe); /* free frontend allocation */ + } + mutex_unlock(&f->lock); +} +EXPORT_SYMBOL(videobuf_dvb_dealloc_frontends); diff --git a/drivers/media/v4l2-core/videobuf-vmalloc.c b/drivers/media/v4l2-core/videobuf-vmalloc.c new file mode 100644 index 000000000000..df142580e44c --- /dev/null +++ b/drivers/media/v4l2-core/videobuf-vmalloc.c @@ -0,0 +1,349 @@ +/* + * helper functions for vmalloc video4linux capture buffers + * + * The functions expect the hardware being able to scatter gather + * (i.e. the buffers are not linear in physical memory, but fragmented + * into PAGE_SIZE chunks). They also assume the driver does not need + * to touch the video data. + * + * (c) 2007 Mauro Carvalho Chehab, <mchehab@infradead.org> + * + * 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 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include <media/videobuf-vmalloc.h> + +#define MAGIC_DMABUF 0x17760309 +#define MAGIC_VMAL_MEM 0x18221223 + +#define MAGIC_CHECK(is, should) \ + if (unlikely((is) != (should))) { \ + printk(KERN_ERR "magic mismatch: %x (expected %x)\n", \ + is, should); \ + BUG(); \ + } + +static int debug; +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("helper module to manage video4linux vmalloc buffers"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>"); +MODULE_LICENSE("GPL"); + +#define dprintk(level, fmt, arg...) \ + if (debug >= level) \ + printk(KERN_DEBUG "vbuf-vmalloc: " fmt , ## arg) + + +/***************************************************************************/ + +static void videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + dprintk(2, "vm_open %p [count=%u,vma=%08lx-%08lx]\n", map, + map->count, vma->vm_start, vma->vm_end); + + map->count++; +} + +static void videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + struct videobuf_queue *q = map->q; + int i; + + dprintk(2, "vm_close %p [count=%u,vma=%08lx-%08lx]\n", map, + map->count, vma->vm_start, vma->vm_end); + + map->count--; + if (0 == map->count) { + struct videobuf_vmalloc_memory *mem; + + dprintk(1, "munmap %p q=%p\n", map, q); + videobuf_queue_lock(q); + + /* We need first to cancel streams, before unmapping */ + if (q->streaming) + videobuf_queue_cancel(q); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + + if (q->bufs[i]->map != map) + continue; + + mem = q->bufs[i]->priv; + if (mem) { + /* This callback is called only if kernel has + allocated memory and this memory is mmapped. + In this case, memory should be freed, + in order to do memory unmap. + */ + + MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM); + + /* vfree is not atomic - can't be + called with IRQ's disabled + */ + dprintk(1, "%s: buf[%d] freeing (%p)\n", + __func__, i, mem->vaddr); + + vfree(mem->vaddr); + mem->vaddr = NULL; + } + + q->bufs[i]->map = NULL; + q->bufs[i]->baddr = 0; + } + + kfree(map); + + videobuf_queue_unlock(q); + } + + return; +} + +static const struct vm_operations_struct videobuf_vm_ops = { + .open = videobuf_vm_open, + .close = videobuf_vm_close, +}; + +/* --------------------------------------------------------------------- + * vmalloc handlers for the generic methods + */ + +/* Allocated area consists on 3 parts: + struct video_buffer + struct <driver>_buffer (cx88_buffer, saa7134_buf, ...) + struct videobuf_dma_sg_memory + */ + +static struct videobuf_buffer *__videobuf_alloc_vb(size_t size) +{ + struct videobuf_vmalloc_memory *mem; + struct videobuf_buffer *vb; + + vb = kzalloc(size + sizeof(*mem), GFP_KERNEL); + if (!vb) + return vb; + + mem = vb->priv = ((char *)vb) + size; + mem->magic = MAGIC_VMAL_MEM; + + dprintk(1, "%s: allocated at %p(%ld+%ld) & %p(%ld)\n", + __func__, vb, (long)sizeof(*vb), (long)size - sizeof(*vb), + mem, (long)sizeof(*mem)); + + return vb; +} + +static int __videobuf_iolock(struct videobuf_queue *q, + struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + struct videobuf_vmalloc_memory *mem = vb->priv; + int pages; + + BUG_ON(!mem); + + MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM); + + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + dprintk(1, "%s memory method MMAP\n", __func__); + + /* All handling should be done by __videobuf_mmap_mapper() */ + if (!mem->vaddr) { + printk(KERN_ERR "memory is not alloced/mmapped.\n"); + return -EINVAL; + } + break; + case V4L2_MEMORY_USERPTR: + pages = PAGE_ALIGN(vb->size); + + dprintk(1, "%s memory method USERPTR\n", __func__); + + if (vb->baddr) { + printk(KERN_ERR "USERPTR is currently not supported\n"); + return -EINVAL; + } + + /* The only USERPTR currently supported is the one needed for + * read() method. + */ + + mem->vaddr = vmalloc_user(pages); + if (!mem->vaddr) { + printk(KERN_ERR "vmalloc (%d pages) failed\n", pages); + return -ENOMEM; + } + dprintk(1, "vmalloc is at addr %p (%d pages)\n", + mem->vaddr, pages); + +#if 0 + int rc; + /* Kernel userptr is used also by read() method. In this case, + there's no need to remap, since data will be copied to user + */ + if (!vb->baddr) + return 0; + + /* FIXME: to properly support USERPTR, remap should occur. + The code below won't work, since mem->vma = NULL + */ + /* Try to remap memory */ + rc = remap_vmalloc_range(mem->vma, (void *)vb->baddr, 0); + if (rc < 0) { + printk(KERN_ERR "mmap: remap failed with error %d", rc); + return -ENOMEM; + } +#endif + + break; + case V4L2_MEMORY_OVERLAY: + default: + dprintk(1, "%s memory method OVERLAY/unknown\n", __func__); + + /* Currently, doesn't support V4L2_MEMORY_OVERLAY */ + printk(KERN_ERR "Memory method currently unsupported.\n"); + return -EINVAL; + } + + return 0; +} + +static int __videobuf_mmap_mapper(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma) +{ + struct videobuf_vmalloc_memory *mem; + struct videobuf_mapping *map; + int retval, pages; + + dprintk(1, "%s\n", __func__); + + /* create mapping + update buffer list */ + map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); + if (NULL == map) + return -ENOMEM; + + buf->map = map; + map->q = q; + + buf->baddr = vma->vm_start; + + mem = buf->priv; + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM); + + pages = PAGE_ALIGN(vma->vm_end - vma->vm_start); + mem->vaddr = vmalloc_user(pages); + if (!mem->vaddr) { + printk(KERN_ERR "vmalloc (%d pages) failed\n", pages); + goto error; + } + dprintk(1, "vmalloc is at addr %p (%d pages)\n", mem->vaddr, pages); + + /* Try to remap memory */ + retval = remap_vmalloc_range(vma, mem->vaddr, 0); + if (retval < 0) { + printk(KERN_ERR "mmap: remap failed with error %d. ", retval); + vfree(mem->vaddr); + goto error; + } + + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED; + vma->vm_private_data = map; + + dprintk(1, "mmap %p: q=%p %08lx-%08lx (%lx) pgoff %08lx buf %d\n", + map, q, vma->vm_start, vma->vm_end, + (long int)buf->bsize, + vma->vm_pgoff, buf->i); + + videobuf_vm_open(vma); + + return 0; + +error: + mem = NULL; + kfree(map); + return -ENOMEM; +} + +static struct videobuf_qtype_ops qops = { + .magic = MAGIC_QTYPE_OPS, + + .alloc_vb = __videobuf_alloc_vb, + .iolock = __videobuf_iolock, + .mmap_mapper = __videobuf_mmap_mapper, + .vaddr = videobuf_to_vmalloc, +}; + +void videobuf_queue_vmalloc_init(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct mutex *ext_lock) +{ + videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize, + priv, &qops, ext_lock); +} +EXPORT_SYMBOL_GPL(videobuf_queue_vmalloc_init); + +void *videobuf_to_vmalloc(struct videobuf_buffer *buf) +{ + struct videobuf_vmalloc_memory *mem = buf->priv; + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM); + + return mem->vaddr; +} +EXPORT_SYMBOL_GPL(videobuf_to_vmalloc); + +void videobuf_vmalloc_free(struct videobuf_buffer *buf) +{ + struct videobuf_vmalloc_memory *mem = buf->priv; + + /* mmapped memory can't be freed here, otherwise mmapped region + would be released, while still needed. In this case, the memory + release should happen inside videobuf_vm_close(). + So, it should free memory only if the memory were allocated for + read() operation. + */ + if ((buf->memory != V4L2_MEMORY_USERPTR) || buf->baddr) + return; + + if (!mem) + return; + + MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM); + + vfree(mem->vaddr); + mem->vaddr = NULL; + + return; +} +EXPORT_SYMBOL_GPL(videobuf_vmalloc_free); + diff --git a/drivers/media/v4l2-core/videobuf2-core.c b/drivers/media/v4l2-core/videobuf2-core.c new file mode 100644 index 000000000000..4da3df61901f --- /dev/null +++ b/drivers/media/v4l2-core/videobuf2-core.c @@ -0,0 +1,2380 @@ +/* + * videobuf2-core.c - V4L2 driver helper framework + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <pawel@osciak.com> + * Marek Szyprowski <m.szyprowski@samsung.com> + * + * 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. + */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-core.h> + +static int debug; +module_param(debug, int, 0644); + +#define dprintk(level, fmt, arg...) \ + do { \ + if (debug >= level) \ + printk(KERN_DEBUG "vb2: " fmt, ## arg); \ + } while (0) + +#define call_memop(q, op, args...) \ + (((q)->mem_ops->op) ? \ + ((q)->mem_ops->op(args)) : 0) + +#define call_qop(q, op, args...) \ + (((q)->ops->op) ? ((q)->ops->op(args)) : 0) + +#define V4L2_BUFFER_STATE_FLAGS (V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | \ + V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_ERROR | \ + V4L2_BUF_FLAG_PREPARED) + +/** + * __vb2_buf_mem_alloc() - allocate video memory for the given buffer + */ +static int __vb2_buf_mem_alloc(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + void *mem_priv; + int plane; + + /* Allocate memory for all planes in this buffer */ + for (plane = 0; plane < vb->num_planes; ++plane) { + mem_priv = call_memop(q, alloc, q->alloc_ctx[plane], + q->plane_sizes[plane]); + if (IS_ERR_OR_NULL(mem_priv)) + goto free; + + /* Associate allocator private data with this plane */ + vb->planes[plane].mem_priv = mem_priv; + vb->v4l2_planes[plane].length = q->plane_sizes[plane]; + } + + return 0; +free: + /* Free already allocated memory if one of the allocations failed */ + for (; plane > 0; --plane) { + call_memop(q, put, vb->planes[plane - 1].mem_priv); + vb->planes[plane - 1].mem_priv = NULL; + } + + return -ENOMEM; +} + +/** + * __vb2_buf_mem_free() - free memory of the given buffer + */ +static void __vb2_buf_mem_free(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + unsigned int plane; + + for (plane = 0; plane < vb->num_planes; ++plane) { + call_memop(q, put, vb->planes[plane].mem_priv); + vb->planes[plane].mem_priv = NULL; + dprintk(3, "Freed plane %d of buffer %d\n", plane, + vb->v4l2_buf.index); + } +} + +/** + * __vb2_buf_userptr_put() - release userspace memory associated with + * a USERPTR buffer + */ +static void __vb2_buf_userptr_put(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + unsigned int plane; + + for (plane = 0; plane < vb->num_planes; ++plane) { + if (vb->planes[plane].mem_priv) + call_memop(q, put_userptr, vb->planes[plane].mem_priv); + vb->planes[plane].mem_priv = NULL; + } +} + +/** + * __setup_offsets() - setup unique offsets ("cookies") for every plane in + * every buffer on the queue + */ +static void __setup_offsets(struct vb2_queue *q, unsigned int n) +{ + unsigned int buffer, plane; + struct vb2_buffer *vb; + unsigned long off; + + if (q->num_buffers) { + struct v4l2_plane *p; + vb = q->bufs[q->num_buffers - 1]; + p = &vb->v4l2_planes[vb->num_planes - 1]; + off = PAGE_ALIGN(p->m.mem_offset + p->length); + } else { + off = 0; + } + + for (buffer = q->num_buffers; buffer < q->num_buffers + n; ++buffer) { + vb = q->bufs[buffer]; + if (!vb) + continue; + + for (plane = 0; plane < vb->num_planes; ++plane) { + vb->v4l2_planes[plane].length = q->plane_sizes[plane]; + vb->v4l2_planes[plane].m.mem_offset = off; + + dprintk(3, "Buffer %d, plane %d offset 0x%08lx\n", + buffer, plane, off); + + off += vb->v4l2_planes[plane].length; + off = PAGE_ALIGN(off); + } + } +} + +/** + * __vb2_queue_alloc() - allocate videobuf buffer structures and (for MMAP type) + * video buffer memory for all buffers/planes on the queue and initializes the + * queue + * + * Returns the number of buffers successfully allocated. + */ +static int __vb2_queue_alloc(struct vb2_queue *q, enum v4l2_memory memory, + unsigned int num_buffers, unsigned int num_planes) +{ + unsigned int buffer; + struct vb2_buffer *vb; + int ret; + + for (buffer = 0; buffer < num_buffers; ++buffer) { + /* Allocate videobuf buffer structures */ + vb = kzalloc(q->buf_struct_size, GFP_KERNEL); + if (!vb) { + dprintk(1, "Memory alloc for buffer struct failed\n"); + break; + } + + /* Length stores number of planes for multiplanar buffers */ + if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) + vb->v4l2_buf.length = num_planes; + + vb->state = VB2_BUF_STATE_DEQUEUED; + vb->vb2_queue = q; + vb->num_planes = num_planes; + vb->v4l2_buf.index = q->num_buffers + buffer; + vb->v4l2_buf.type = q->type; + vb->v4l2_buf.memory = memory; + + /* Allocate video buffer memory for the MMAP type */ + if (memory == V4L2_MEMORY_MMAP) { + ret = __vb2_buf_mem_alloc(vb); + if (ret) { + dprintk(1, "Failed allocating memory for " + "buffer %d\n", buffer); + kfree(vb); + break; + } + /* + * Call the driver-provided buffer initialization + * callback, if given. An error in initialization + * results in queue setup failure. + */ + ret = call_qop(q, buf_init, vb); + if (ret) { + dprintk(1, "Buffer %d %p initialization" + " failed\n", buffer, vb); + __vb2_buf_mem_free(vb); + kfree(vb); + break; + } + } + + q->bufs[q->num_buffers + buffer] = vb; + } + + __setup_offsets(q, buffer); + + dprintk(1, "Allocated %d buffers, %d plane(s) each\n", + buffer, num_planes); + + return buffer; +} + +/** + * __vb2_free_mem() - release all video buffer memory for a given queue + */ +static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers) +{ + unsigned int buffer; + struct vb2_buffer *vb; + + for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; + ++buffer) { + vb = q->bufs[buffer]; + if (!vb) + continue; + + /* Free MMAP buffers or release USERPTR buffers */ + if (q->memory == V4L2_MEMORY_MMAP) + __vb2_buf_mem_free(vb); + else + __vb2_buf_userptr_put(vb); + } +} + +/** + * __vb2_queue_free() - free buffers at the end of the queue - video memory and + * related information, if no buffers are left return the queue to an + * uninitialized state. Might be called even if the queue has already been freed. + */ +static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) +{ + unsigned int buffer; + + /* Call driver-provided cleanup function for each buffer, if provided */ + if (q->ops->buf_cleanup) { + for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; + ++buffer) { + if (NULL == q->bufs[buffer]) + continue; + q->ops->buf_cleanup(q->bufs[buffer]); + } + } + + /* Release video buffer memory */ + __vb2_free_mem(q, buffers); + + /* Free videobuf buffers */ + for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; + ++buffer) { + kfree(q->bufs[buffer]); + q->bufs[buffer] = NULL; + } + + q->num_buffers -= buffers; + if (!q->num_buffers) + q->memory = 0; + INIT_LIST_HEAD(&q->queued_list); +} + +/** + * __verify_planes_array() - verify that the planes array passed in struct + * v4l2_buffer from userspace can be safely used + */ +static int __verify_planes_array(struct vb2_buffer *vb, const struct v4l2_buffer *b) +{ + /* Is memory for copying plane information present? */ + if (NULL == b->m.planes) { + dprintk(1, "Multi-planar buffer passed but " + "planes array not provided\n"); + return -EINVAL; + } + + if (b->length < vb->num_planes || b->length > VIDEO_MAX_PLANES) { + dprintk(1, "Incorrect planes array length, " + "expected %d, got %d\n", vb->num_planes, b->length); + return -EINVAL; + } + + return 0; +} + +/** + * __buffer_in_use() - return true if the buffer is in use and + * the queue cannot be freed (by the means of REQBUFS(0)) call + */ +static bool __buffer_in_use(struct vb2_queue *q, struct vb2_buffer *vb) +{ + unsigned int plane; + for (plane = 0; plane < vb->num_planes; ++plane) { + void *mem_priv = vb->planes[plane].mem_priv; + /* + * If num_users() has not been provided, call_memop + * will return 0, apparently nobody cares about this + * case anyway. If num_users() returns more than 1, + * we are not the only user of the plane's memory. + */ + if (mem_priv && call_memop(q, num_users, mem_priv) > 1) + return true; + } + return false; +} + +/** + * __buffers_in_use() - return true if any buffers on the queue are in use and + * the queue cannot be freed (by the means of REQBUFS(0)) call + */ +static bool __buffers_in_use(struct vb2_queue *q) +{ + unsigned int buffer; + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + if (__buffer_in_use(q, q->bufs[buffer])) + return true; + } + return false; +} + +/** + * __fill_v4l2_buffer() - fill in a struct v4l2_buffer with information to be + * returned to userspace + */ +static int __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b) +{ + struct vb2_queue *q = vb->vb2_queue; + int ret; + + /* Copy back data such as timestamp, flags, etc. */ + memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m)); + b->reserved2 = vb->v4l2_buf.reserved2; + b->reserved = vb->v4l2_buf.reserved; + + if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) { + ret = __verify_planes_array(vb, b); + if (ret) + return ret; + + /* + * Fill in plane-related data if userspace provided an array + * for it. The memory and size is verified above. + */ + memcpy(b->m.planes, vb->v4l2_planes, + b->length * sizeof(struct v4l2_plane)); + } else { + /* + * We use length and offset in v4l2_planes array even for + * single-planar buffers, but userspace does not. + */ + b->length = vb->v4l2_planes[0].length; + b->bytesused = vb->v4l2_planes[0].bytesused; + if (q->memory == V4L2_MEMORY_MMAP) + b->m.offset = vb->v4l2_planes[0].m.mem_offset; + else if (q->memory == V4L2_MEMORY_USERPTR) + b->m.userptr = vb->v4l2_planes[0].m.userptr; + } + + /* + * Clear any buffer state related flags. + */ + b->flags &= ~V4L2_BUFFER_STATE_FLAGS; + + switch (vb->state) { + case VB2_BUF_STATE_QUEUED: + case VB2_BUF_STATE_ACTIVE: + b->flags |= V4L2_BUF_FLAG_QUEUED; + break; + case VB2_BUF_STATE_ERROR: + b->flags |= V4L2_BUF_FLAG_ERROR; + /* fall through */ + case VB2_BUF_STATE_DONE: + b->flags |= V4L2_BUF_FLAG_DONE; + break; + case VB2_BUF_STATE_PREPARED: + b->flags |= V4L2_BUF_FLAG_PREPARED; + break; + case VB2_BUF_STATE_DEQUEUED: + /* nothing */ + break; + } + + if (__buffer_in_use(q, vb)) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + return 0; +} + +/** + * vb2_querybuf() - query video buffer information + * @q: videobuf queue + * @b: buffer struct passed from userspace to vidioc_querybuf handler + * in driver + * + * Should be called from vidioc_querybuf ioctl handler in driver. + * This function will verify the passed v4l2_buffer structure and fill the + * relevant information for the userspace. + * + * The return values from this function are intended to be directly returned + * from vidioc_querybuf handler in driver. + */ +int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + struct vb2_buffer *vb; + + if (b->type != q->type) { + dprintk(1, "querybuf: wrong buffer type\n"); + return -EINVAL; + } + + if (b->index >= q->num_buffers) { + dprintk(1, "querybuf: buffer index out of range\n"); + return -EINVAL; + } + vb = q->bufs[b->index]; + + return __fill_v4l2_buffer(vb, b); +} +EXPORT_SYMBOL(vb2_querybuf); + +/** + * __verify_userptr_ops() - verify that all memory operations required for + * USERPTR queue type have been provided + */ +static int __verify_userptr_ops(struct vb2_queue *q) +{ + if (!(q->io_modes & VB2_USERPTR) || !q->mem_ops->get_userptr || + !q->mem_ops->put_userptr) + return -EINVAL; + + return 0; +} + +/** + * __verify_mmap_ops() - verify that all memory operations required for + * MMAP queue type have been provided + */ +static int __verify_mmap_ops(struct vb2_queue *q) +{ + if (!(q->io_modes & VB2_MMAP) || !q->mem_ops->alloc || + !q->mem_ops->put || !q->mem_ops->mmap) + return -EINVAL; + + return 0; +} + +/** + * __verify_memory_type() - Check whether the memory type and buffer type + * passed to a buffer operation are compatible with the queue. + */ +static int __verify_memory_type(struct vb2_queue *q, + enum v4l2_memory memory, enum v4l2_buf_type type) +{ + if (memory != V4L2_MEMORY_MMAP && memory != V4L2_MEMORY_USERPTR) { + dprintk(1, "reqbufs: unsupported memory type\n"); + return -EINVAL; + } + + if (type != q->type) { + dprintk(1, "reqbufs: requested type is incorrect\n"); + return -EINVAL; + } + + /* + * Make sure all the required memory ops for given memory type + * are available. + */ + if (memory == V4L2_MEMORY_MMAP && __verify_mmap_ops(q)) { + dprintk(1, "reqbufs: MMAP for current setup unsupported\n"); + return -EINVAL; + } + + if (memory == V4L2_MEMORY_USERPTR && __verify_userptr_ops(q)) { + dprintk(1, "reqbufs: USERPTR for current setup unsupported\n"); + return -EINVAL; + } + + /* + * Place the busy tests at the end: -EBUSY can be ignored when + * create_bufs is called with count == 0, but count == 0 should still + * do the memory and type validation. + */ + if (q->fileio) { + dprintk(1, "reqbufs: file io in progress\n"); + return -EBUSY; + } + return 0; +} + +/** + * __reqbufs() - Initiate streaming + * @q: videobuf2 queue + * @req: struct passed from userspace to vidioc_reqbufs handler in driver + * + * Should be called from vidioc_reqbufs ioctl handler of a driver. + * This function: + * 1) verifies streaming parameters passed from the userspace, + * 2) sets up the queue, + * 3) negotiates number of buffers and planes per buffer with the driver + * to be used during streaming, + * 4) allocates internal buffer structures (struct vb2_buffer), according to + * the agreed parameters, + * 5) for MMAP memory type, allocates actual video memory, using the + * memory handling/allocation routines provided during queue initialization + * + * If req->count is 0, all the memory will be freed instead. + * If the queue has been allocated previously (by a previous vb2_reqbufs) call + * and the queue is not busy, memory will be reallocated. + * + * The return values from this function are intended to be directly returned + * from vidioc_reqbufs handler in driver. + */ +static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req) +{ + unsigned int num_buffers, allocated_buffers, num_planes = 0; + int ret; + + if (q->streaming) { + dprintk(1, "reqbufs: streaming active\n"); + return -EBUSY; + } + + if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) { + /* + * We already have buffers allocated, so first check if they + * are not in use and can be freed. + */ + if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) { + dprintk(1, "reqbufs: memory in use, cannot free\n"); + return -EBUSY; + } + + __vb2_queue_free(q, q->num_buffers); + + /* + * In case of REQBUFS(0) return immediately without calling + * driver's queue_setup() callback and allocating resources. + */ + if (req->count == 0) + return 0; + } + + /* + * Make sure the requested values and current defaults are sane. + */ + num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME); + memset(q->plane_sizes, 0, sizeof(q->plane_sizes)); + memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx)); + q->memory = req->memory; + + /* + * Ask the driver how many buffers and planes per buffer it requires. + * Driver also sets the size and allocator context for each plane. + */ + ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes, + q->plane_sizes, q->alloc_ctx); + if (ret) + return ret; + + /* Finally, allocate buffers and video memory */ + ret = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes); + if (ret == 0) { + dprintk(1, "Memory allocation failed\n"); + return -ENOMEM; + } + + allocated_buffers = ret; + + /* + * Check if driver can handle the allocated number of buffers. + */ + if (allocated_buffers < num_buffers) { + num_buffers = allocated_buffers; + + ret = call_qop(q, queue_setup, q, NULL, &num_buffers, + &num_planes, q->plane_sizes, q->alloc_ctx); + + if (!ret && allocated_buffers < num_buffers) + ret = -ENOMEM; + + /* + * Either the driver has accepted a smaller number of buffers, + * or .queue_setup() returned an error + */ + } + + q->num_buffers = allocated_buffers; + + if (ret < 0) { + __vb2_queue_free(q, allocated_buffers); + return ret; + } + + /* + * Return the number of successfully allocated buffers + * to the userspace. + */ + req->count = allocated_buffers; + + return 0; +} + +/** + * vb2_reqbufs() - Wrapper for __reqbufs() that also verifies the memory and + * type values. + * @q: videobuf2 queue + * @req: struct passed from userspace to vidioc_reqbufs handler in driver + */ +int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req) +{ + int ret = __verify_memory_type(q, req->memory, req->type); + + return ret ? ret : __reqbufs(q, req); +} +EXPORT_SYMBOL_GPL(vb2_reqbufs); + +/** + * __create_bufs() - Allocate buffers and any required auxiliary structs + * @q: videobuf2 queue + * @create: creation parameters, passed from userspace to vidioc_create_bufs + * handler in driver + * + * Should be called from vidioc_create_bufs ioctl handler of a driver. + * This function: + * 1) verifies parameter sanity + * 2) calls the .queue_setup() queue operation + * 3) performs any necessary memory allocations + * + * The return values from this function are intended to be directly returned + * from vidioc_create_bufs handler in driver. + */ +static int __create_bufs(struct vb2_queue *q, struct v4l2_create_buffers *create) +{ + unsigned int num_planes = 0, num_buffers, allocated_buffers; + int ret; + + if (q->num_buffers == VIDEO_MAX_FRAME) { + dprintk(1, "%s(): maximum number of buffers already allocated\n", + __func__); + return -ENOBUFS; + } + + if (!q->num_buffers) { + memset(q->plane_sizes, 0, sizeof(q->plane_sizes)); + memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx)); + q->memory = create->memory; + } + + num_buffers = min(create->count, VIDEO_MAX_FRAME - q->num_buffers); + + /* + * Ask the driver, whether the requested number of buffers, planes per + * buffer and their sizes are acceptable + */ + ret = call_qop(q, queue_setup, q, &create->format, &num_buffers, + &num_planes, q->plane_sizes, q->alloc_ctx); + if (ret) + return ret; + + /* Finally, allocate buffers and video memory */ + ret = __vb2_queue_alloc(q, create->memory, num_buffers, + num_planes); + if (ret == 0) { + dprintk(1, "Memory allocation failed\n"); + return -ENOMEM; + } + + allocated_buffers = ret; + + /* + * Check if driver can handle the so far allocated number of buffers. + */ + if (ret < num_buffers) { + num_buffers = ret; + + /* + * q->num_buffers contains the total number of buffers, that the + * queue driver has set up + */ + ret = call_qop(q, queue_setup, q, &create->format, &num_buffers, + &num_planes, q->plane_sizes, q->alloc_ctx); + + if (!ret && allocated_buffers < num_buffers) + ret = -ENOMEM; + + /* + * Either the driver has accepted a smaller number of buffers, + * or .queue_setup() returned an error + */ + } + + q->num_buffers += allocated_buffers; + + if (ret < 0) { + __vb2_queue_free(q, allocated_buffers); + return -ENOMEM; + } + + /* + * Return the number of successfully allocated buffers + * to the userspace. + */ + create->count = allocated_buffers; + + return 0; +} + +/** + * vb2_create_bufs() - Wrapper for __create_bufs() that also verifies the + * memory and type values. + * @q: videobuf2 queue + * @create: creation parameters, passed from userspace to vidioc_create_bufs + * handler in driver + */ +int vb2_create_bufs(struct vb2_queue *q, struct v4l2_create_buffers *create) +{ + int ret = __verify_memory_type(q, create->memory, create->format.type); + + create->index = q->num_buffers; + if (create->count == 0) + return ret != -EBUSY ? ret : 0; + return ret ? ret : __create_bufs(q, create); +} +EXPORT_SYMBOL_GPL(vb2_create_bufs); + +/** + * vb2_plane_vaddr() - Return a kernel virtual address of a given plane + * @vb: vb2_buffer to which the plane in question belongs to + * @plane_no: plane number for which the address is to be returned + * + * This function returns a kernel virtual address of a given plane if + * such a mapping exist, NULL otherwise. + */ +void *vb2_plane_vaddr(struct vb2_buffer *vb, unsigned int plane_no) +{ + struct vb2_queue *q = vb->vb2_queue; + + if (plane_no > vb->num_planes || !vb->planes[plane_no].mem_priv) + return NULL; + + return call_memop(q, vaddr, vb->planes[plane_no].mem_priv); + +} +EXPORT_SYMBOL_GPL(vb2_plane_vaddr); + +/** + * vb2_plane_cookie() - Return allocator specific cookie for the given plane + * @vb: vb2_buffer to which the plane in question belongs to + * @plane_no: plane number for which the cookie is to be returned + * + * This function returns an allocator specific cookie for a given plane if + * available, NULL otherwise. The allocator should provide some simple static + * inline function, which would convert this cookie to the allocator specific + * type that can be used directly by the driver to access the buffer. This can + * be for example physical address, pointer to scatter list or IOMMU mapping. + */ +void *vb2_plane_cookie(struct vb2_buffer *vb, unsigned int plane_no) +{ + struct vb2_queue *q = vb->vb2_queue; + + if (plane_no > vb->num_planes || !vb->planes[plane_no].mem_priv) + return NULL; + + return call_memop(q, cookie, vb->planes[plane_no].mem_priv); +} +EXPORT_SYMBOL_GPL(vb2_plane_cookie); + +/** + * vb2_buffer_done() - inform videobuf that an operation on a buffer is finished + * @vb: vb2_buffer returned from the driver + * @state: either VB2_BUF_STATE_DONE if the operation finished successfully + * or VB2_BUF_STATE_ERROR if the operation finished with an error + * + * This function should be called by the driver after a hardware operation on + * a buffer is finished and the buffer may be returned to userspace. The driver + * cannot use this buffer anymore until it is queued back to it by videobuf + * by the means of buf_queue callback. Only buffers previously queued to the + * driver by buf_queue can be passed to this function. + */ +void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state) +{ + struct vb2_queue *q = vb->vb2_queue; + unsigned long flags; + + if (vb->state != VB2_BUF_STATE_ACTIVE) + return; + + if (state != VB2_BUF_STATE_DONE && state != VB2_BUF_STATE_ERROR) + return; + + dprintk(4, "Done processing on buffer %d, state: %d\n", + vb->v4l2_buf.index, vb->state); + + /* Add the buffer to the done buffers list */ + spin_lock_irqsave(&q->done_lock, flags); + vb->state = state; + list_add_tail(&vb->done_entry, &q->done_list); + atomic_dec(&q->queued_count); + spin_unlock_irqrestore(&q->done_lock, flags); + + /* Inform any processes that may be waiting for buffers */ + wake_up(&q->done_wq); +} +EXPORT_SYMBOL_GPL(vb2_buffer_done); + +/** + * __fill_vb2_buffer() - fill a vb2_buffer with information provided in + * a v4l2_buffer by the userspace + */ +static int __fill_vb2_buffer(struct vb2_buffer *vb, const struct v4l2_buffer *b, + struct v4l2_plane *v4l2_planes) +{ + unsigned int plane; + int ret; + + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { + /* + * Verify that the userspace gave us a valid array for + * plane information. + */ + ret = __verify_planes_array(vb, b); + if (ret) + return ret; + + /* Fill in driver-provided information for OUTPUT types */ + if (V4L2_TYPE_IS_OUTPUT(b->type)) { + /* + * Will have to go up to b->length when API starts + * accepting variable number of planes. + */ + for (plane = 0; plane < vb->num_planes; ++plane) { + v4l2_planes[plane].bytesused = + b->m.planes[plane].bytesused; + v4l2_planes[plane].data_offset = + b->m.planes[plane].data_offset; + } + } + + if (b->memory == V4L2_MEMORY_USERPTR) { + for (plane = 0; plane < vb->num_planes; ++plane) { + v4l2_planes[plane].m.userptr = + b->m.planes[plane].m.userptr; + v4l2_planes[plane].length = + b->m.planes[plane].length; + } + } + } else { + /* + * Single-planar buffers do not use planes array, + * so fill in relevant v4l2_buffer struct fields instead. + * In videobuf we use our internal V4l2_planes struct for + * single-planar buffers as well, for simplicity. + */ + if (V4L2_TYPE_IS_OUTPUT(b->type)) + v4l2_planes[0].bytesused = b->bytesused; + + if (b->memory == V4L2_MEMORY_USERPTR) { + v4l2_planes[0].m.userptr = b->m.userptr; + v4l2_planes[0].length = b->length; + } + } + + vb->v4l2_buf.field = b->field; + vb->v4l2_buf.timestamp = b->timestamp; + vb->v4l2_buf.flags = b->flags & ~V4L2_BUFFER_STATE_FLAGS; + + return 0; +} + +/** + * __qbuf_userptr() - handle qbuf of a USERPTR buffer + */ +static int __qbuf_userptr(struct vb2_buffer *vb, const struct v4l2_buffer *b) +{ + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + struct vb2_queue *q = vb->vb2_queue; + void *mem_priv; + unsigned int plane; + int ret; + int write = !V4L2_TYPE_IS_OUTPUT(q->type); + + /* Verify and copy relevant information provided by the userspace */ + ret = __fill_vb2_buffer(vb, b, planes); + if (ret) + return ret; + + for (plane = 0; plane < vb->num_planes; ++plane) { + /* Skip the plane if already verified */ + if (vb->v4l2_planes[plane].m.userptr && + vb->v4l2_planes[plane].m.userptr == planes[plane].m.userptr + && vb->v4l2_planes[plane].length == planes[plane].length) + continue; + + dprintk(3, "qbuf: userspace address for plane %d changed, " + "reacquiring memory\n", plane); + + /* Check if the provided plane buffer is large enough */ + if (planes[plane].length < q->plane_sizes[plane]) { + ret = -EINVAL; + goto err; + } + + /* Release previously acquired memory if present */ + if (vb->planes[plane].mem_priv) + call_memop(q, put_userptr, vb->planes[plane].mem_priv); + + vb->planes[plane].mem_priv = NULL; + vb->v4l2_planes[plane].m.userptr = 0; + vb->v4l2_planes[plane].length = 0; + + /* Acquire each plane's memory */ + mem_priv = call_memop(q, get_userptr, q->alloc_ctx[plane], + planes[plane].m.userptr, + planes[plane].length, write); + if (IS_ERR_OR_NULL(mem_priv)) { + dprintk(1, "qbuf: failed acquiring userspace " + "memory for plane %d\n", plane); + ret = mem_priv ? PTR_ERR(mem_priv) : -EINVAL; + goto err; + } + vb->planes[plane].mem_priv = mem_priv; + } + + /* + * Call driver-specific initialization on the newly acquired buffer, + * if provided. + */ + ret = call_qop(q, buf_init, vb); + if (ret) { + dprintk(1, "qbuf: buffer initialization failed\n"); + goto err; + } + + /* + * Now that everything is in order, copy relevant information + * provided by userspace. + */ + for (plane = 0; plane < vb->num_planes; ++plane) + vb->v4l2_planes[plane] = planes[plane]; + + return 0; +err: + /* In case of errors, release planes that were already acquired */ + for (plane = 0; plane < vb->num_planes; ++plane) { + if (vb->planes[plane].mem_priv) + call_memop(q, put_userptr, vb->planes[plane].mem_priv); + vb->planes[plane].mem_priv = NULL; + vb->v4l2_planes[plane].m.userptr = 0; + vb->v4l2_planes[plane].length = 0; + } + + return ret; +} + +/** + * __qbuf_mmap() - handle qbuf of an MMAP buffer + */ +static int __qbuf_mmap(struct vb2_buffer *vb, const struct v4l2_buffer *b) +{ + return __fill_vb2_buffer(vb, b, vb->v4l2_planes); +} + +/** + * __enqueue_in_driver() - enqueue a vb2_buffer in driver for processing + */ +static void __enqueue_in_driver(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + + vb->state = VB2_BUF_STATE_ACTIVE; + atomic_inc(&q->queued_count); + q->ops->buf_queue(vb); +} + +static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b) +{ + struct vb2_queue *q = vb->vb2_queue; + int ret; + + switch (q->memory) { + case V4L2_MEMORY_MMAP: + ret = __qbuf_mmap(vb, b); + break; + case V4L2_MEMORY_USERPTR: + ret = __qbuf_userptr(vb, b); + break; + default: + WARN(1, "Invalid queue type\n"); + ret = -EINVAL; + } + + if (!ret) + ret = call_qop(q, buf_prepare, vb); + if (ret) + dprintk(1, "qbuf: buffer preparation failed: %d\n", ret); + else + vb->state = VB2_BUF_STATE_PREPARED; + + return ret; +} + +/** + * vb2_prepare_buf() - Pass ownership of a buffer from userspace to the kernel + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_prepare_buf + * handler in driver + * + * Should be called from vidioc_prepare_buf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) calls buf_prepare callback in the driver (if provided), in which + * driver-specific buffer initialization can be performed, + * + * The return values from this function are intended to be directly returned + * from vidioc_prepare_buf handler in driver. + */ +int vb2_prepare_buf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + struct vb2_buffer *vb; + int ret; + + if (q->fileio) { + dprintk(1, "%s(): file io in progress\n", __func__); + return -EBUSY; + } + + if (b->type != q->type) { + dprintk(1, "%s(): invalid buffer type\n", __func__); + return -EINVAL; + } + + if (b->index >= q->num_buffers) { + dprintk(1, "%s(): buffer index out of range\n", __func__); + return -EINVAL; + } + + vb = q->bufs[b->index]; + if (NULL == vb) { + /* Should never happen */ + dprintk(1, "%s(): buffer is NULL\n", __func__); + return -EINVAL; + } + + if (b->memory != q->memory) { + dprintk(1, "%s(): invalid memory type\n", __func__); + return -EINVAL; + } + + if (vb->state != VB2_BUF_STATE_DEQUEUED) { + dprintk(1, "%s(): invalid buffer state %d\n", __func__, vb->state); + return -EINVAL; + } + + ret = __buf_prepare(vb, b); + if (ret < 0) + return ret; + + __fill_v4l2_buffer(vb, b); + + return 0; +} +EXPORT_SYMBOL_GPL(vb2_prepare_buf); + +/** + * vb2_qbuf() - Queue a buffer from userspace + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_qbuf handler + * in driver + * + * Should be called from vidioc_qbuf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) if necessary, calls buf_prepare callback in the driver (if provided), in + * which driver-specific buffer initialization can be performed, + * 3) if streaming is on, queues the buffer in driver by the means of buf_queue + * callback for processing. + * + * The return values from this function are intended to be directly returned + * from vidioc_qbuf handler in driver. + */ +int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + struct rw_semaphore *mmap_sem = NULL; + struct vb2_buffer *vb; + int ret = 0; + + /* + * In case of user pointer buffers vb2 allocator needs to get direct + * access to userspace pages. This requires getting read access on + * mmap semaphore in the current process structure. The same + * semaphore is taken before calling mmap operation, while both mmap + * and qbuf are called by the driver or v4l2 core with driver's lock + * held. To avoid a AB-BA deadlock (mmap_sem then driver's lock in + * mmap and driver's lock then mmap_sem in qbuf) the videobuf2 core + * release driver's lock, takes mmap_sem and then takes again driver's + * lock. + * + * To avoid race with other vb2 calls, which might be called after + * releasing driver's lock, this operation is performed at the + * beggining of qbuf processing. This way the queue status is + * consistent after getting driver's lock back. + */ + if (q->memory == V4L2_MEMORY_USERPTR) { + mmap_sem = ¤t->mm->mmap_sem; + call_qop(q, wait_prepare, q); + down_read(mmap_sem); + call_qop(q, wait_finish, q); + } + + if (q->fileio) { + dprintk(1, "qbuf: file io in progress\n"); + ret = -EBUSY; + goto unlock; + } + + if (b->type != q->type) { + dprintk(1, "qbuf: invalid buffer type\n"); + ret = -EINVAL; + goto unlock; + } + + if (b->index >= q->num_buffers) { + dprintk(1, "qbuf: buffer index out of range\n"); + ret = -EINVAL; + goto unlock; + } + + vb = q->bufs[b->index]; + if (NULL == vb) { + /* Should never happen */ + dprintk(1, "qbuf: buffer is NULL\n"); + ret = -EINVAL; + goto unlock; + } + + if (b->memory != q->memory) { + dprintk(1, "qbuf: invalid memory type\n"); + ret = -EINVAL; + goto unlock; + } + + switch (vb->state) { + case VB2_BUF_STATE_DEQUEUED: + ret = __buf_prepare(vb, b); + if (ret) + goto unlock; + case VB2_BUF_STATE_PREPARED: + break; + default: + dprintk(1, "qbuf: buffer already in use\n"); + ret = -EINVAL; + goto unlock; + } + + /* + * Add to the queued buffers list, a buffer will stay on it until + * dequeued in dqbuf. + */ + list_add_tail(&vb->queued_entry, &q->queued_list); + vb->state = VB2_BUF_STATE_QUEUED; + + /* + * If already streaming, give the buffer to driver for processing. + * If not, the buffer will be given to driver on next streamon. + */ + if (q->streaming) + __enqueue_in_driver(vb); + + /* Fill buffer information for the userspace */ + __fill_v4l2_buffer(vb, b); + + dprintk(1, "qbuf of buffer %d succeeded\n", vb->v4l2_buf.index); +unlock: + if (mmap_sem) + up_read(mmap_sem); + return ret; +} +EXPORT_SYMBOL_GPL(vb2_qbuf); + +/** + * __vb2_wait_for_done_vb() - wait for a buffer to become available + * for dequeuing + * + * Will sleep if required for nonblocking == false. + */ +static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking) +{ + /* + * All operations on vb_done_list are performed under done_lock + * spinlock protection. However, buffers may be removed from + * it and returned to userspace only while holding both driver's + * lock and the done_lock spinlock. Thus we can be sure that as + * long as we hold the driver's lock, the list will remain not + * empty if list_empty() check succeeds. + */ + + for (;;) { + int ret; + + if (!q->streaming) { + dprintk(1, "Streaming off, will not wait for buffers\n"); + return -EINVAL; + } + + if (!list_empty(&q->done_list)) { + /* + * Found a buffer that we were waiting for. + */ + break; + } + + if (nonblocking) { + dprintk(1, "Nonblocking and no buffers to dequeue, " + "will not wait\n"); + return -EAGAIN; + } + + /* + * We are streaming and blocking, wait for another buffer to + * become ready or for streamoff. Driver's lock is released to + * allow streamoff or qbuf to be called while waiting. + */ + call_qop(q, wait_prepare, q); + + /* + * All locks have been released, it is safe to sleep now. + */ + dprintk(3, "Will sleep waiting for buffers\n"); + ret = wait_event_interruptible(q->done_wq, + !list_empty(&q->done_list) || !q->streaming); + + /* + * We need to reevaluate both conditions again after reacquiring + * the locks or return an error if one occurred. + */ + call_qop(q, wait_finish, q); + if (ret) + return ret; + } + return 0; +} + +/** + * __vb2_get_done_vb() - get a buffer ready for dequeuing + * + * Will sleep if required for nonblocking == false. + */ +static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb, + int nonblocking) +{ + unsigned long flags; + int ret; + + /* + * Wait for at least one buffer to become available on the done_list. + */ + ret = __vb2_wait_for_done_vb(q, nonblocking); + if (ret) + return ret; + + /* + * Driver's lock has been held since we last verified that done_list + * is not empty, so no need for another list_empty(done_list) check. + */ + spin_lock_irqsave(&q->done_lock, flags); + *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry); + list_del(&(*vb)->done_entry); + spin_unlock_irqrestore(&q->done_lock, flags); + + return 0; +} + +/** + * vb2_wait_for_all_buffers() - wait until all buffers are given back to vb2 + * @q: videobuf2 queue + * + * This function will wait until all buffers that have been given to the driver + * by buf_queue() are given back to vb2 with vb2_buffer_done(). It doesn't call + * wait_prepare, wait_finish pair. It is intended to be called with all locks + * taken, for example from stop_streaming() callback. + */ +int vb2_wait_for_all_buffers(struct vb2_queue *q) +{ + if (!q->streaming) { + dprintk(1, "Streaming off, will not wait for buffers\n"); + return -EINVAL; + } + + wait_event(q->done_wq, !atomic_read(&q->queued_count)); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_wait_for_all_buffers); + +/** + * vb2_dqbuf() - Dequeue a buffer to the userspace + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_dqbuf handler + * in driver + * @nonblocking: if true, this call will not sleep waiting for a buffer if no + * buffers ready for dequeuing are present. Normally the driver + * would be passing (file->f_flags & O_NONBLOCK) here + * + * Should be called from vidioc_dqbuf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) calls buf_finish callback in the driver (if provided), in which + * driver can perform any additional operations that may be required before + * returning the buffer to userspace, such as cache sync, + * 3) the buffer struct members are filled with relevant information for + * the userspace. + * + * The return values from this function are intended to be directly returned + * from vidioc_dqbuf handler in driver. + */ +int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) +{ + struct vb2_buffer *vb = NULL; + int ret; + + if (q->fileio) { + dprintk(1, "dqbuf: file io in progress\n"); + return -EBUSY; + } + + if (b->type != q->type) { + dprintk(1, "dqbuf: invalid buffer type\n"); + return -EINVAL; + } + + ret = __vb2_get_done_vb(q, &vb, nonblocking); + if (ret < 0) { + dprintk(1, "dqbuf: error getting next done buffer\n"); + return ret; + } + + ret = call_qop(q, buf_finish, vb); + if (ret) { + dprintk(1, "dqbuf: buffer finish failed\n"); + return ret; + } + + switch (vb->state) { + case VB2_BUF_STATE_DONE: + dprintk(3, "dqbuf: Returning done buffer\n"); + break; + case VB2_BUF_STATE_ERROR: + dprintk(3, "dqbuf: Returning done buffer with errors\n"); + break; + default: + dprintk(1, "dqbuf: Invalid buffer state\n"); + return -EINVAL; + } + + /* Fill buffer information for the userspace */ + __fill_v4l2_buffer(vb, b); + /* Remove from videobuf queue */ + list_del(&vb->queued_entry); + + dprintk(1, "dqbuf of buffer %d, with state %d\n", + vb->v4l2_buf.index, vb->state); + + vb->state = VB2_BUF_STATE_DEQUEUED; + return 0; +} +EXPORT_SYMBOL_GPL(vb2_dqbuf); + +/** + * __vb2_queue_cancel() - cancel and stop (pause) streaming + * + * Removes all queued buffers from driver's queue and all buffers queued by + * userspace from videobuf's queue. Returns to state after reqbufs. + */ +static void __vb2_queue_cancel(struct vb2_queue *q) +{ + unsigned int i; + + /* + * Tell driver to stop all transactions and release all queued + * buffers. + */ + if (q->streaming) + call_qop(q, stop_streaming, q); + q->streaming = 0; + + /* + * Remove all buffers from videobuf's list... + */ + INIT_LIST_HEAD(&q->queued_list); + /* + * ...and done list; userspace will not receive any buffers it + * has not already dequeued before initiating cancel. + */ + INIT_LIST_HEAD(&q->done_list); + atomic_set(&q->queued_count, 0); + wake_up_all(&q->done_wq); + + /* + * Reinitialize all buffers for next use. + */ + for (i = 0; i < q->num_buffers; ++i) + q->bufs[i]->state = VB2_BUF_STATE_DEQUEUED; +} + +/** + * vb2_streamon - start streaming + * @q: videobuf2 queue + * @type: type argument passed from userspace to vidioc_streamon handler + * + * Should be called from vidioc_streamon handler of a driver. + * This function: + * 1) verifies current state + * 2) passes any previously queued buffers to the driver and starts streaming + * + * The return values from this function are intended to be directly returned + * from vidioc_streamon handler in the driver. + */ +int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type) +{ + struct vb2_buffer *vb; + int ret; + + if (q->fileio) { + dprintk(1, "streamon: file io in progress\n"); + return -EBUSY; + } + + if (type != q->type) { + dprintk(1, "streamon: invalid stream type\n"); + return -EINVAL; + } + + if (q->streaming) { + dprintk(1, "streamon: already streaming\n"); + return -EBUSY; + } + + /* + * If any buffers were queued before streamon, + * we can now pass them to driver for processing. + */ + list_for_each_entry(vb, &q->queued_list, queued_entry) + __enqueue_in_driver(vb); + + /* + * Let driver notice that streaming state has been enabled. + */ + ret = call_qop(q, start_streaming, q, atomic_read(&q->queued_count)); + if (ret) { + dprintk(1, "streamon: driver refused to start streaming\n"); + __vb2_queue_cancel(q); + return ret; + } + + q->streaming = 1; + + dprintk(3, "Streamon successful\n"); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_streamon); + + +/** + * vb2_streamoff - stop streaming + * @q: videobuf2 queue + * @type: type argument passed from userspace to vidioc_streamoff handler + * + * Should be called from vidioc_streamoff handler of a driver. + * This function: + * 1) verifies current state, + * 2) stop streaming and dequeues any queued buffers, including those previously + * passed to the driver (after waiting for the driver to finish). + * + * This call can be used for pausing playback. + * The return values from this function are intended to be directly returned + * from vidioc_streamoff handler in the driver + */ +int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type) +{ + if (q->fileio) { + dprintk(1, "streamoff: file io in progress\n"); + return -EBUSY; + } + + if (type != q->type) { + dprintk(1, "streamoff: invalid stream type\n"); + return -EINVAL; + } + + if (!q->streaming) { + dprintk(1, "streamoff: not streaming\n"); + return -EINVAL; + } + + /* + * Cancel will pause streaming and remove all buffers from the driver + * and videobuf, effectively returning control over them to userspace. + */ + __vb2_queue_cancel(q); + + dprintk(3, "Streamoff successful\n"); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_streamoff); + +/** + * __find_plane_by_offset() - find plane associated with the given offset off + */ +static int __find_plane_by_offset(struct vb2_queue *q, unsigned long off, + unsigned int *_buffer, unsigned int *_plane) +{ + struct vb2_buffer *vb; + unsigned int buffer, plane; + + /* + * Go over all buffers and their planes, comparing the given offset + * with an offset assigned to each plane. If a match is found, + * return its buffer and plane numbers. + */ + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + vb = q->bufs[buffer]; + + for (plane = 0; plane < vb->num_planes; ++plane) { + if (vb->v4l2_planes[plane].m.mem_offset == off) { + *_buffer = buffer; + *_plane = plane; + return 0; + } + } + } + + return -EINVAL; +} + +/** + * vb2_mmap() - map video buffers into application address space + * @q: videobuf2 queue + * @vma: vma passed to the mmap file operation handler in the driver + * + * Should be called from mmap file operation handler of a driver. + * This function maps one plane of one of the available video buffers to + * userspace. To map whole video memory allocated on reqbufs, this function + * has to be called once per each plane per each buffer previously allocated. + * + * When the userspace application calls mmap, it passes to it an offset returned + * to it earlier by the means of vidioc_querybuf handler. That offset acts as + * a "cookie", which is then used to identify the plane to be mapped. + * This function finds a plane with a matching offset and a mapping is performed + * by the means of a provided memory operation. + * + * The return values from this function are intended to be directly returned + * from the mmap handler in driver. + */ +int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma) +{ + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + struct vb2_buffer *vb; + unsigned int buffer, plane; + int ret; + + if (q->memory != V4L2_MEMORY_MMAP) { + dprintk(1, "Queue is not currently set up for mmap\n"); + return -EINVAL; + } + + /* + * Check memory area access mode. + */ + if (!(vma->vm_flags & VM_SHARED)) { + dprintk(1, "Invalid vma flags, VM_SHARED needed\n"); + return -EINVAL; + } + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + if (!(vma->vm_flags & VM_WRITE)) { + dprintk(1, "Invalid vma flags, VM_WRITE needed\n"); + return -EINVAL; + } + } else { + if (!(vma->vm_flags & VM_READ)) { + dprintk(1, "Invalid vma flags, VM_READ needed\n"); + return -EINVAL; + } + } + + /* + * Find the plane corresponding to the offset passed by userspace. + */ + ret = __find_plane_by_offset(q, off, &buffer, &plane); + if (ret) + return ret; + + vb = q->bufs[buffer]; + + ret = call_memop(q, mmap, vb->planes[plane].mem_priv, vma); + if (ret) + return ret; + + dprintk(3, "Buffer %d, plane %d successfully mapped\n", buffer, plane); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_mmap); + +#ifndef CONFIG_MMU +unsigned long vb2_get_unmapped_area(struct vb2_queue *q, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + unsigned long off = pgoff << PAGE_SHIFT; + struct vb2_buffer *vb; + unsigned int buffer, plane; + int ret; + + if (q->memory != V4L2_MEMORY_MMAP) { + dprintk(1, "Queue is not currently set up for mmap\n"); + return -EINVAL; + } + + /* + * Find the plane corresponding to the offset passed by userspace. + */ + ret = __find_plane_by_offset(q, off, &buffer, &plane); + if (ret) + return ret; + + vb = q->bufs[buffer]; + + return (unsigned long)vb2_plane_vaddr(vb, plane); +} +EXPORT_SYMBOL_GPL(vb2_get_unmapped_area); +#endif + +static int __vb2_init_fileio(struct vb2_queue *q, int read); +static int __vb2_cleanup_fileio(struct vb2_queue *q); + +/** + * vb2_poll() - implements poll userspace operation + * @q: videobuf2 queue + * @file: file argument passed to the poll file operation handler + * @wait: wait argument passed to the poll file operation handler + * + * This function implements poll file operation handler for a driver. + * For CAPTURE queues, if a buffer is ready to be dequeued, the userspace will + * be informed that the file descriptor of a video device is available for + * reading. + * For OUTPUT queues, if a buffer is ready to be dequeued, the file descriptor + * will be reported as available for writing. + * + * If the driver uses struct v4l2_fh, then vb2_poll() will also check for any + * pending events. + * + * The return values from this function are intended to be directly returned + * from poll handler in driver. + */ +unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait) +{ + struct video_device *vfd = video_devdata(file); + unsigned long req_events = poll_requested_events(wait); + struct vb2_buffer *vb = NULL; + unsigned int res = 0; + unsigned long flags; + + if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) { + struct v4l2_fh *fh = file->private_data; + + if (v4l2_event_pending(fh)) + res = POLLPRI; + else if (req_events & POLLPRI) + poll_wait(file, &fh->wait, wait); + } + + /* + * Start file I/O emulator only if streaming API has not been used yet. + */ + if (q->num_buffers == 0 && q->fileio == NULL) { + if (!V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_READ) && + (req_events & (POLLIN | POLLRDNORM))) { + if (__vb2_init_fileio(q, 1)) + return res | POLLERR; + } + if (V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_WRITE) && + (req_events & (POLLOUT | POLLWRNORM))) { + if (__vb2_init_fileio(q, 0)) + return res | POLLERR; + /* + * Write to OUTPUT queue can be done immediately. + */ + return res | POLLOUT | POLLWRNORM; + } + } + + /* + * There is nothing to wait for if no buffers have already been queued. + */ + if (list_empty(&q->queued_list)) + return res | POLLERR; + + poll_wait(file, &q->done_wq, wait); + + /* + * Take first buffer available for dequeuing. + */ + spin_lock_irqsave(&q->done_lock, flags); + if (!list_empty(&q->done_list)) + vb = list_first_entry(&q->done_list, struct vb2_buffer, + done_entry); + spin_unlock_irqrestore(&q->done_lock, flags); + + if (vb && (vb->state == VB2_BUF_STATE_DONE + || vb->state == VB2_BUF_STATE_ERROR)) { + return (V4L2_TYPE_IS_OUTPUT(q->type)) ? + res | POLLOUT | POLLWRNORM : + res | POLLIN | POLLRDNORM; + } + return res; +} +EXPORT_SYMBOL_GPL(vb2_poll); + +/** + * vb2_queue_init() - initialize a videobuf2 queue + * @q: videobuf2 queue; this structure should be allocated in driver + * + * The vb2_queue structure should be allocated by the driver. The driver is + * responsible of clearing it's content and setting initial values for some + * required entries before calling this function. + * q->ops, q->mem_ops, q->type and q->io_modes are mandatory. Please refer + * to the struct vb2_queue description in include/media/videobuf2-core.h + * for more information. + */ +int vb2_queue_init(struct vb2_queue *q) +{ + BUG_ON(!q); + BUG_ON(!q->ops); + BUG_ON(!q->mem_ops); + BUG_ON(!q->type); + BUG_ON(!q->io_modes); + + BUG_ON(!q->ops->queue_setup); + BUG_ON(!q->ops->buf_queue); + + INIT_LIST_HEAD(&q->queued_list); + INIT_LIST_HEAD(&q->done_list); + spin_lock_init(&q->done_lock); + init_waitqueue_head(&q->done_wq); + + if (q->buf_struct_size == 0) + q->buf_struct_size = sizeof(struct vb2_buffer); + + return 0; +} +EXPORT_SYMBOL_GPL(vb2_queue_init); + +/** + * vb2_queue_release() - stop streaming, release the queue and free memory + * @q: videobuf2 queue + * + * This function stops streaming and performs necessary clean ups, including + * freeing video buffer memory. The driver is responsible for freeing + * the vb2_queue structure itself. + */ +void vb2_queue_release(struct vb2_queue *q) +{ + __vb2_cleanup_fileio(q); + __vb2_queue_cancel(q); + __vb2_queue_free(q, q->num_buffers); +} +EXPORT_SYMBOL_GPL(vb2_queue_release); + +/** + * struct vb2_fileio_buf - buffer context used by file io emulator + * + * vb2 provides a compatibility layer and emulator of file io (read and + * write) calls on top of streaming API. This structure is used for + * tracking context related to the buffers. + */ +struct vb2_fileio_buf { + void *vaddr; + unsigned int size; + unsigned int pos; + unsigned int queued:1; +}; + +/** + * struct vb2_fileio_data - queue context used by file io emulator + * + * vb2 provides a compatibility layer and emulator of file io (read and + * write) calls on top of streaming API. For proper operation it required + * this structure to save the driver state between each call of the read + * or write function. + */ +struct vb2_fileio_data { + struct v4l2_requestbuffers req; + struct v4l2_buffer b; + struct vb2_fileio_buf bufs[VIDEO_MAX_FRAME]; + unsigned int index; + unsigned int q_count; + unsigned int dq_count; + unsigned int flags; +}; + +/** + * __vb2_init_fileio() - initialize file io emulator + * @q: videobuf2 queue + * @read: mode selector (1 means read, 0 means write) + */ +static int __vb2_init_fileio(struct vb2_queue *q, int read) +{ + struct vb2_fileio_data *fileio; + int i, ret; + unsigned int count = 0; + + /* + * Sanity check + */ + if ((read && !(q->io_modes & VB2_READ)) || + (!read && !(q->io_modes & VB2_WRITE))) + BUG(); + + /* + * Check if device supports mapping buffers to kernel virtual space. + */ + if (!q->mem_ops->vaddr) + return -EBUSY; + + /* + * Check if streaming api has not been already activated. + */ + if (q->streaming || q->num_buffers > 0) + return -EBUSY; + + /* + * Start with count 1, driver can increase it in queue_setup() + */ + count = 1; + + dprintk(3, "setting up file io: mode %s, count %d, flags %08x\n", + (read) ? "read" : "write", count, q->io_flags); + + fileio = kzalloc(sizeof(struct vb2_fileio_data), GFP_KERNEL); + if (fileio == NULL) + return -ENOMEM; + + fileio->flags = q->io_flags; + + /* + * Request buffers and use MMAP type to force driver + * to allocate buffers by itself. + */ + fileio->req.count = count; + fileio->req.memory = V4L2_MEMORY_MMAP; + fileio->req.type = q->type; + ret = vb2_reqbufs(q, &fileio->req); + if (ret) + goto err_kfree; + + /* + * Check if plane_count is correct + * (multiplane buffers are not supported). + */ + if (q->bufs[0]->num_planes != 1) { + ret = -EBUSY; + goto err_reqbufs; + } + + /* + * Get kernel address of each buffer. + */ + for (i = 0; i < q->num_buffers; i++) { + fileio->bufs[i].vaddr = vb2_plane_vaddr(q->bufs[i], 0); + if (fileio->bufs[i].vaddr == NULL) + goto err_reqbufs; + fileio->bufs[i].size = vb2_plane_size(q->bufs[i], 0); + } + + /* + * Read mode requires pre queuing of all buffers. + */ + if (read) { + /* + * Queue all buffers. + */ + for (i = 0; i < q->num_buffers; i++) { + struct v4l2_buffer *b = &fileio->b; + memset(b, 0, sizeof(*b)); + b->type = q->type; + b->memory = q->memory; + b->index = i; + ret = vb2_qbuf(q, b); + if (ret) + goto err_reqbufs; + fileio->bufs[i].queued = 1; + } + + /* + * Start streaming. + */ + ret = vb2_streamon(q, q->type); + if (ret) + goto err_reqbufs; + } + + q->fileio = fileio; + + return ret; + +err_reqbufs: + fileio->req.count = 0; + vb2_reqbufs(q, &fileio->req); + +err_kfree: + kfree(fileio); + return ret; +} + +/** + * __vb2_cleanup_fileio() - free resourced used by file io emulator + * @q: videobuf2 queue + */ +static int __vb2_cleanup_fileio(struct vb2_queue *q) +{ + struct vb2_fileio_data *fileio = q->fileio; + + if (fileio) { + /* + * Hack fileio context to enable direct calls to vb2 ioctl + * interface. + */ + q->fileio = NULL; + + vb2_streamoff(q, q->type); + fileio->req.count = 0; + vb2_reqbufs(q, &fileio->req); + kfree(fileio); + dprintk(3, "file io emulator closed\n"); + } + return 0; +} + +/** + * __vb2_perform_fileio() - perform a single file io (read or write) operation + * @q: videobuf2 queue + * @data: pointed to target userspace buffer + * @count: number of bytes to read or write + * @ppos: file handle position tracking pointer + * @nonblock: mode selector (1 means blocking calls, 0 means nonblocking) + * @read: access mode selector (1 means read, 0 means write) + */ +static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_t count, + loff_t *ppos, int nonblock, int read) +{ + struct vb2_fileio_data *fileio; + struct vb2_fileio_buf *buf; + int ret, index; + + dprintk(3, "file io: mode %s, offset %ld, count %zd, %sblocking\n", + read ? "read" : "write", (long)*ppos, count, + nonblock ? "non" : ""); + + if (!data) + return -EINVAL; + + /* + * Initialize emulator on first call. + */ + if (!q->fileio) { + ret = __vb2_init_fileio(q, read); + dprintk(3, "file io: vb2_init_fileio result: %d\n", ret); + if (ret) + return ret; + } + fileio = q->fileio; + + /* + * Hack fileio context to enable direct calls to vb2 ioctl interface. + * The pointer will be restored before returning from this function. + */ + q->fileio = NULL; + + index = fileio->index; + buf = &fileio->bufs[index]; + + /* + * Check if we need to dequeue the buffer. + */ + if (buf->queued) { + struct vb2_buffer *vb; + + /* + * Call vb2_dqbuf to get buffer back. + */ + memset(&fileio->b, 0, sizeof(fileio->b)); + fileio->b.type = q->type; + fileio->b.memory = q->memory; + fileio->b.index = index; + ret = vb2_dqbuf(q, &fileio->b, nonblock); + dprintk(5, "file io: vb2_dqbuf result: %d\n", ret); + if (ret) + goto end; + fileio->dq_count += 1; + + /* + * Get number of bytes filled by the driver + */ + vb = q->bufs[index]; + buf->size = vb2_get_plane_payload(vb, 0); + buf->queued = 0; + } + + /* + * Limit count on last few bytes of the buffer. + */ + if (buf->pos + count > buf->size) { + count = buf->size - buf->pos; + dprintk(5, "reducing read count: %zd\n", count); + } + + /* + * Transfer data to userspace. + */ + dprintk(3, "file io: copying %zd bytes - buffer %d, offset %u\n", + count, index, buf->pos); + if (read) + ret = copy_to_user(data, buf->vaddr + buf->pos, count); + else + ret = copy_from_user(buf->vaddr + buf->pos, data, count); + if (ret) { + dprintk(3, "file io: error copying data\n"); + ret = -EFAULT; + goto end; + } + + /* + * Update counters. + */ + buf->pos += count; + *ppos += count; + + /* + * Queue next buffer if required. + */ + if (buf->pos == buf->size || + (!read && (fileio->flags & VB2_FILEIO_WRITE_IMMEDIATELY))) { + /* + * Check if this is the last buffer to read. + */ + if (read && (fileio->flags & VB2_FILEIO_READ_ONCE) && + fileio->dq_count == 1) { + dprintk(3, "file io: read limit reached\n"); + /* + * Restore fileio pointer and release the context. + */ + q->fileio = fileio; + return __vb2_cleanup_fileio(q); + } + + /* + * Call vb2_qbuf and give buffer to the driver. + */ + memset(&fileio->b, 0, sizeof(fileio->b)); + fileio->b.type = q->type; + fileio->b.memory = q->memory; + fileio->b.index = index; + fileio->b.bytesused = buf->pos; + ret = vb2_qbuf(q, &fileio->b); + dprintk(5, "file io: vb2_dbuf result: %d\n", ret); + if (ret) + goto end; + + /* + * Buffer has been queued, update the status + */ + buf->pos = 0; + buf->queued = 1; + buf->size = q->bufs[0]->v4l2_planes[0].length; + fileio->q_count += 1; + + /* + * Switch to the next buffer + */ + fileio->index = (index + 1) % q->num_buffers; + + /* + * Start streaming if required. + */ + if (!read && !q->streaming) { + ret = vb2_streamon(q, q->type); + if (ret) + goto end; + } + } + + /* + * Return proper number of bytes processed. + */ + if (ret == 0) + ret = count; +end: + /* + * Restore the fileio context and block vb2 ioctl interface. + */ + q->fileio = fileio; + return ret; +} + +size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count, + loff_t *ppos, int nonblocking) +{ + return __vb2_perform_fileio(q, data, count, ppos, nonblocking, 1); +} +EXPORT_SYMBOL_GPL(vb2_read); + +size_t vb2_write(struct vb2_queue *q, char __user *data, size_t count, + loff_t *ppos, int nonblocking) +{ + return __vb2_perform_fileio(q, data, count, ppos, nonblocking, 0); +} +EXPORT_SYMBOL_GPL(vb2_write); + + +/* + * The following functions are not part of the vb2 core API, but are helper + * functions that plug into struct v4l2_ioctl_ops, struct v4l2_file_operations + * and struct vb2_ops. + * They contain boilerplate code that most if not all drivers have to do + * and so they simplify the driver code. + */ + +/* The queue is busy if there is a owner and you are not that owner. */ +static inline bool vb2_queue_is_busy(struct video_device *vdev, struct file *file) +{ + return vdev->queue->owner && vdev->queue->owner != file->private_data; +} + +/* vb2 ioctl helpers */ + +int vb2_ioctl_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct video_device *vdev = video_devdata(file); + int res = __verify_memory_type(vdev->queue, p->memory, p->type); + + if (res) + return res; + if (vb2_queue_is_busy(vdev, file)) + return -EBUSY; + res = __reqbufs(vdev->queue, p); + /* If count == 0, then the owner has released all buffers and he + is no longer owner of the queue. Otherwise we have a new owner. */ + if (res == 0) + vdev->queue->owner = p->count ? file->private_data : NULL; + return res; +} +EXPORT_SYMBOL_GPL(vb2_ioctl_reqbufs); + +int vb2_ioctl_create_bufs(struct file *file, void *priv, + struct v4l2_create_buffers *p) +{ + struct video_device *vdev = video_devdata(file); + int res = __verify_memory_type(vdev->queue, p->memory, p->format.type); + + p->index = vdev->queue->num_buffers; + /* If count == 0, then just check if memory and type are valid. + Any -EBUSY result from __verify_memory_type can be mapped to 0. */ + if (p->count == 0) + return res != -EBUSY ? res : 0; + if (res) + return res; + if (vb2_queue_is_busy(vdev, file)) + return -EBUSY; + res = __create_bufs(vdev->queue, p); + if (res == 0) + vdev->queue->owner = file->private_data; + return res; +} +EXPORT_SYMBOL_GPL(vb2_ioctl_create_bufs); + +int vb2_ioctl_prepare_buf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct video_device *vdev = video_devdata(file); + + if (vb2_queue_is_busy(vdev, file)) + return -EBUSY; + return vb2_prepare_buf(vdev->queue, p); +} +EXPORT_SYMBOL_GPL(vb2_ioctl_prepare_buf); + +int vb2_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct video_device *vdev = video_devdata(file); + + /* No need to call vb2_queue_is_busy(), anyone can query buffers. */ + return vb2_querybuf(vdev->queue, p); +} +EXPORT_SYMBOL_GPL(vb2_ioctl_querybuf); + +int vb2_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct video_device *vdev = video_devdata(file); + + if (vb2_queue_is_busy(vdev, file)) + return -EBUSY; + return vb2_qbuf(vdev->queue, p); +} +EXPORT_SYMBOL_GPL(vb2_ioctl_qbuf); + +int vb2_ioctl_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct video_device *vdev = video_devdata(file); + + if (vb2_queue_is_busy(vdev, file)) + return -EBUSY; + return vb2_dqbuf(vdev->queue, p, file->f_flags & O_NONBLOCK); +} +EXPORT_SYMBOL_GPL(vb2_ioctl_dqbuf); + +int vb2_ioctl_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdev = video_devdata(file); + + if (vb2_queue_is_busy(vdev, file)) + return -EBUSY; + return vb2_streamon(vdev->queue, i); +} +EXPORT_SYMBOL_GPL(vb2_ioctl_streamon); + +int vb2_ioctl_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdev = video_devdata(file); + + if (vb2_queue_is_busy(vdev, file)) + return -EBUSY; + return vb2_streamoff(vdev->queue, i); +} +EXPORT_SYMBOL_GPL(vb2_ioctl_streamoff); + +/* v4l2_file_operations helpers */ + +int vb2_fop_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + + return vb2_mmap(vdev->queue, vma); +} +EXPORT_SYMBOL_GPL(vb2_fop_mmap); + +int vb2_fop_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + + if (file->private_data == vdev->queue->owner) { + vb2_queue_release(vdev->queue); + vdev->queue->owner = NULL; + } + return v4l2_fh_release(file); +} +EXPORT_SYMBOL_GPL(vb2_fop_release); + +ssize_t vb2_fop_write(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct mutex *lock = vdev->queue->lock ? vdev->queue->lock : vdev->lock; + int err = -EBUSY; + + if (lock && mutex_lock_interruptible(lock)) + return -ERESTARTSYS; + if (vb2_queue_is_busy(vdev, file)) + goto exit; + err = vb2_write(vdev->queue, buf, count, ppos, + file->f_flags & O_NONBLOCK); + if (err >= 0) + vdev->queue->owner = file->private_data; +exit: + if (lock) + mutex_unlock(lock); + return err; +} +EXPORT_SYMBOL_GPL(vb2_fop_write); + +ssize_t vb2_fop_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct mutex *lock = vdev->queue->lock ? vdev->queue->lock : vdev->lock; + int err = -EBUSY; + + if (lock && mutex_lock_interruptible(lock)) + return -ERESTARTSYS; + if (vb2_queue_is_busy(vdev, file)) + goto exit; + err = vb2_read(vdev->queue, buf, count, ppos, + file->f_flags & O_NONBLOCK); + if (err >= 0) + vdev->queue->owner = file->private_data; +exit: + if (lock) + mutex_unlock(lock); + return err; +} +EXPORT_SYMBOL_GPL(vb2_fop_read); + +unsigned int vb2_fop_poll(struct file *file, poll_table *wait) +{ + struct video_device *vdev = video_devdata(file); + struct vb2_queue *q = vdev->queue; + struct mutex *lock = q->lock ? q->lock : vdev->lock; + unsigned long req_events = poll_requested_events(wait); + unsigned res; + void *fileio; + bool must_lock = false; + + /* Try to be smart: only lock if polling might start fileio, + otherwise locking will only introduce unwanted delays. */ + if (q->num_buffers == 0 && q->fileio == NULL) { + if (!V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_READ) && + (req_events & (POLLIN | POLLRDNORM))) + must_lock = true; + else if (V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_WRITE) && + (req_events & (POLLOUT | POLLWRNORM))) + must_lock = true; + } + + /* If locking is needed, but this helper doesn't know how, then you + shouldn't be using this helper but you should write your own. */ + WARN_ON(must_lock && !lock); + + if (must_lock && lock && mutex_lock_interruptible(lock)) + return POLLERR; + + fileio = q->fileio; + + res = vb2_poll(vdev->queue, file, wait); + + /* If fileio was started, then we have a new queue owner. */ + if (must_lock && !fileio && q->fileio) + q->owner = file->private_data; + if (must_lock && lock) + mutex_unlock(lock); + return res; +} +EXPORT_SYMBOL_GPL(vb2_fop_poll); + +#ifndef CONFIG_MMU +unsigned long vb2_fop_get_unmapped_area(struct file *file, unsigned long addr, + unsigned long len, unsigned long pgoff, unsigned long flags) +{ + struct video_device *vdev = video_devdata(file); + + return vb2_get_unmapped_area(vdev->queue, addr, len, pgoff, flags); +} +EXPORT_SYMBOL_GPL(vb2_fop_get_unmapped_area); +#endif + +/* vb2_ops helpers. Only use if vq->lock is non-NULL. */ + +void vb2_ops_wait_prepare(struct vb2_queue *vq) +{ + mutex_unlock(vq->lock); +} +EXPORT_SYMBOL_GPL(vb2_ops_wait_prepare); + +void vb2_ops_wait_finish(struct vb2_queue *vq) +{ + mutex_lock(vq->lock); +} +EXPORT_SYMBOL_GPL(vb2_ops_wait_finish); + +MODULE_DESCRIPTION("Driver helper framework for Video for Linux 2"); +MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>, Marek Szyprowski"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/videobuf2-dma-contig.c b/drivers/media/v4l2-core/videobuf2-dma-contig.c new file mode 100644 index 000000000000..4b7132660a93 --- /dev/null +++ b/drivers/media/v4l2-core/videobuf2-dma-contig.c @@ -0,0 +1,186 @@ +/* + * videobuf2-dma-contig.c - DMA contig memory allocator for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <pawel@osciak.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-memops.h> + +struct vb2_dc_conf { + struct device *dev; +}; + +struct vb2_dc_buf { + struct vb2_dc_conf *conf; + void *vaddr; + dma_addr_t dma_addr; + unsigned long size; + struct vm_area_struct *vma; + atomic_t refcount; + struct vb2_vmarea_handler handler; +}; + +static void vb2_dma_contig_put(void *buf_priv); + +static void *vb2_dma_contig_alloc(void *alloc_ctx, unsigned long size) +{ + struct vb2_dc_conf *conf = alloc_ctx; + struct vb2_dc_buf *buf; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + buf->vaddr = dma_alloc_coherent(conf->dev, size, &buf->dma_addr, + GFP_KERNEL); + if (!buf->vaddr) { + dev_err(conf->dev, "dma_alloc_coherent of size %ld failed\n", + size); + kfree(buf); + return ERR_PTR(-ENOMEM); + } + + buf->conf = conf; + buf->size = size; + + buf->handler.refcount = &buf->refcount; + buf->handler.put = vb2_dma_contig_put; + buf->handler.arg = buf; + + atomic_inc(&buf->refcount); + + return buf; +} + +static void vb2_dma_contig_put(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + + if (atomic_dec_and_test(&buf->refcount)) { + dma_free_coherent(buf->conf->dev, buf->size, buf->vaddr, + buf->dma_addr); + kfree(buf); + } +} + +static void *vb2_dma_contig_cookie(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + + return &buf->dma_addr; +} + +static void *vb2_dma_contig_vaddr(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + if (!buf) + return NULL; + + return buf->vaddr; +} + +static unsigned int vb2_dma_contig_num_users(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + + return atomic_read(&buf->refcount); +} + +static int vb2_dma_contig_mmap(void *buf_priv, struct vm_area_struct *vma) +{ + struct vb2_dc_buf *buf = buf_priv; + + if (!buf) { + printk(KERN_ERR "No buffer to map\n"); + return -EINVAL; + } + + return vb2_mmap_pfn_range(vma, buf->dma_addr, buf->size, + &vb2_common_vm_ops, &buf->handler); +} + +static void *vb2_dma_contig_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + struct vb2_dc_buf *buf; + struct vm_area_struct *vma; + dma_addr_t dma_addr = 0; + int ret; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + ret = vb2_get_contig_userptr(vaddr, size, &vma, &dma_addr); + if (ret) { + printk(KERN_ERR "Failed acquiring VMA for vaddr 0x%08lx\n", + vaddr); + kfree(buf); + return ERR_PTR(ret); + } + + buf->size = size; + buf->dma_addr = dma_addr; + buf->vma = vma; + + return buf; +} + +static void vb2_dma_contig_put_userptr(void *mem_priv) +{ + struct vb2_dc_buf *buf = mem_priv; + + if (!buf) + return; + + vb2_put_vma(buf->vma); + kfree(buf); +} + +const struct vb2_mem_ops vb2_dma_contig_memops = { + .alloc = vb2_dma_contig_alloc, + .put = vb2_dma_contig_put, + .cookie = vb2_dma_contig_cookie, + .vaddr = vb2_dma_contig_vaddr, + .mmap = vb2_dma_contig_mmap, + .get_userptr = vb2_dma_contig_get_userptr, + .put_userptr = vb2_dma_contig_put_userptr, + .num_users = vb2_dma_contig_num_users, +}; +EXPORT_SYMBOL_GPL(vb2_dma_contig_memops); + +void *vb2_dma_contig_init_ctx(struct device *dev) +{ + struct vb2_dc_conf *conf; + + conf = kzalloc(sizeof *conf, GFP_KERNEL); + if (!conf) + return ERR_PTR(-ENOMEM); + + conf->dev = dev; + + return conf; +} +EXPORT_SYMBOL_GPL(vb2_dma_contig_init_ctx); + +void vb2_dma_contig_cleanup_ctx(void *alloc_ctx) +{ + kfree(alloc_ctx); +} +EXPORT_SYMBOL_GPL(vb2_dma_contig_cleanup_ctx); + +MODULE_DESCRIPTION("DMA-contig memory handling routines for videobuf2"); +MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/videobuf2-dma-sg.c b/drivers/media/v4l2-core/videobuf2-dma-sg.c new file mode 100644 index 000000000000..25c3b360e1ad --- /dev/null +++ b/drivers/media/v4l2-core/videobuf2-dma-sg.c @@ -0,0 +1,283 @@ +/* + * videobuf2-dma-sg.c - dma scatter/gather memory allocator for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/scatterlist.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-memops.h> +#include <media/videobuf2-dma-sg.h> + +struct vb2_dma_sg_buf { + void *vaddr; + struct page **pages; + int write; + int offset; + struct vb2_dma_sg_desc sg_desc; + atomic_t refcount; + struct vb2_vmarea_handler handler; +}; + +static void vb2_dma_sg_put(void *buf_priv); + +static void *vb2_dma_sg_alloc(void *alloc_ctx, unsigned long size) +{ + struct vb2_dma_sg_buf *buf; + int i; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return NULL; + + buf->vaddr = NULL; + buf->write = 0; + buf->offset = 0; + buf->sg_desc.size = size; + buf->sg_desc.num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + + buf->sg_desc.sglist = vzalloc(buf->sg_desc.num_pages * + sizeof(*buf->sg_desc.sglist)); + if (!buf->sg_desc.sglist) + goto fail_sglist_alloc; + sg_init_table(buf->sg_desc.sglist, buf->sg_desc.num_pages); + + buf->pages = kzalloc(buf->sg_desc.num_pages * sizeof(struct page *), + GFP_KERNEL); + if (!buf->pages) + goto fail_pages_array_alloc; + + for (i = 0; i < buf->sg_desc.num_pages; ++i) { + buf->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN); + if (NULL == buf->pages[i]) + goto fail_pages_alloc; + sg_set_page(&buf->sg_desc.sglist[i], + buf->pages[i], PAGE_SIZE, 0); + } + + buf->handler.refcount = &buf->refcount; + buf->handler.put = vb2_dma_sg_put; + buf->handler.arg = buf; + + atomic_inc(&buf->refcount); + + printk(KERN_DEBUG "%s: Allocated buffer of %d pages\n", + __func__, buf->sg_desc.num_pages); + return buf; + +fail_pages_alloc: + while (--i >= 0) + __free_page(buf->pages[i]); + kfree(buf->pages); + +fail_pages_array_alloc: + vfree(buf->sg_desc.sglist); + +fail_sglist_alloc: + kfree(buf); + return NULL; +} + +static void vb2_dma_sg_put(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + int i = buf->sg_desc.num_pages; + + if (atomic_dec_and_test(&buf->refcount)) { + printk(KERN_DEBUG "%s: Freeing buffer of %d pages\n", __func__, + buf->sg_desc.num_pages); + if (buf->vaddr) + vm_unmap_ram(buf->vaddr, buf->sg_desc.num_pages); + vfree(buf->sg_desc.sglist); + while (--i >= 0) + __free_page(buf->pages[i]); + kfree(buf->pages); + kfree(buf); + } +} + +static void *vb2_dma_sg_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + struct vb2_dma_sg_buf *buf; + unsigned long first, last; + int num_pages_from_user, i; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return NULL; + + buf->vaddr = NULL; + buf->write = write; + buf->offset = vaddr & ~PAGE_MASK; + buf->sg_desc.size = size; + + first = (vaddr & PAGE_MASK) >> PAGE_SHIFT; + last = ((vaddr + size - 1) & PAGE_MASK) >> PAGE_SHIFT; + buf->sg_desc.num_pages = last - first + 1; + + buf->sg_desc.sglist = vzalloc( + buf->sg_desc.num_pages * sizeof(*buf->sg_desc.sglist)); + if (!buf->sg_desc.sglist) + goto userptr_fail_sglist_alloc; + + sg_init_table(buf->sg_desc.sglist, buf->sg_desc.num_pages); + + buf->pages = kzalloc(buf->sg_desc.num_pages * sizeof(struct page *), + GFP_KERNEL); + if (!buf->pages) + goto userptr_fail_pages_array_alloc; + + num_pages_from_user = get_user_pages(current, current->mm, + vaddr & PAGE_MASK, + buf->sg_desc.num_pages, + write, + 1, /* force */ + buf->pages, + NULL); + + if (num_pages_from_user != buf->sg_desc.num_pages) + goto userptr_fail_get_user_pages; + + sg_set_page(&buf->sg_desc.sglist[0], buf->pages[0], + PAGE_SIZE - buf->offset, buf->offset); + size -= PAGE_SIZE - buf->offset; + for (i = 1; i < buf->sg_desc.num_pages; ++i) { + sg_set_page(&buf->sg_desc.sglist[i], buf->pages[i], + min_t(size_t, PAGE_SIZE, size), 0); + size -= min_t(size_t, PAGE_SIZE, size); + } + return buf; + +userptr_fail_get_user_pages: + printk(KERN_DEBUG "get_user_pages requested/got: %d/%d]\n", + num_pages_from_user, buf->sg_desc.num_pages); + while (--num_pages_from_user >= 0) + put_page(buf->pages[num_pages_from_user]); + kfree(buf->pages); + +userptr_fail_pages_array_alloc: + vfree(buf->sg_desc.sglist); + +userptr_fail_sglist_alloc: + kfree(buf); + return NULL; +} + +/* + * @put_userptr: inform the allocator that a USERPTR buffer will no longer + * be used + */ +static void vb2_dma_sg_put_userptr(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + int i = buf->sg_desc.num_pages; + + printk(KERN_DEBUG "%s: Releasing userspace buffer of %d pages\n", + __func__, buf->sg_desc.num_pages); + if (buf->vaddr) + vm_unmap_ram(buf->vaddr, buf->sg_desc.num_pages); + while (--i >= 0) { + if (buf->write) + set_page_dirty_lock(buf->pages[i]); + put_page(buf->pages[i]); + } + vfree(buf->sg_desc.sglist); + kfree(buf->pages); + kfree(buf); +} + +static void *vb2_dma_sg_vaddr(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + + BUG_ON(!buf); + + if (!buf->vaddr) + buf->vaddr = vm_map_ram(buf->pages, + buf->sg_desc.num_pages, + -1, + PAGE_KERNEL); + + /* add offset in case userptr is not page-aligned */ + return buf->vaddr + buf->offset; +} + +static unsigned int vb2_dma_sg_num_users(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + + return atomic_read(&buf->refcount); +} + +static int vb2_dma_sg_mmap(void *buf_priv, struct vm_area_struct *vma) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + unsigned long uaddr = vma->vm_start; + unsigned long usize = vma->vm_end - vma->vm_start; + int i = 0; + + if (!buf) { + printk(KERN_ERR "No memory to map\n"); + return -EINVAL; + } + + do { + int ret; + + ret = vm_insert_page(vma, uaddr, buf->pages[i++]); + if (ret) { + printk(KERN_ERR "Remapping memory, error: %d\n", ret); + return ret; + } + + uaddr += PAGE_SIZE; + usize -= PAGE_SIZE; + } while (usize > 0); + + + /* + * Use common vm_area operations to track buffer refcount. + */ + vma->vm_private_data = &buf->handler; + vma->vm_ops = &vb2_common_vm_ops; + + vma->vm_ops->open(vma); + + return 0; +} + +static void *vb2_dma_sg_cookie(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + + return &buf->sg_desc; +} + +const struct vb2_mem_ops vb2_dma_sg_memops = { + .alloc = vb2_dma_sg_alloc, + .put = vb2_dma_sg_put, + .get_userptr = vb2_dma_sg_get_userptr, + .put_userptr = vb2_dma_sg_put_userptr, + .vaddr = vb2_dma_sg_vaddr, + .mmap = vb2_dma_sg_mmap, + .num_users = vb2_dma_sg_num_users, + .cookie = vb2_dma_sg_cookie, +}; +EXPORT_SYMBOL_GPL(vb2_dma_sg_memops); + +MODULE_DESCRIPTION("dma scatter/gather memory handling routines for videobuf2"); +MODULE_AUTHOR("Andrzej Pietrasiewicz"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/videobuf2-memops.c b/drivers/media/v4l2-core/videobuf2-memops.c new file mode 100644 index 000000000000..504cd4cbe29e --- /dev/null +++ b/drivers/media/v4l2-core/videobuf2-memops.c @@ -0,0 +1,227 @@ +/* + * videobuf2-memops.c - generic memory handling routines for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <pawel@osciak.com> + * Marek Szyprowski <m.szyprowski@samsung.com> + * + * 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. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/file.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-memops.h> + +/** + * vb2_get_vma() - acquire and lock the virtual memory area + * @vma: given virtual memory area + * + * This function attempts to acquire an area mapped in the userspace for + * the duration of a hardware operation. The area is "locked" by performing + * the same set of operation that are done when process calls fork() and + * memory areas are duplicated. + * + * Returns a copy of a virtual memory region on success or NULL. + */ +struct vm_area_struct *vb2_get_vma(struct vm_area_struct *vma) +{ + struct vm_area_struct *vma_copy; + + vma_copy = kmalloc(sizeof(*vma_copy), GFP_KERNEL); + if (vma_copy == NULL) + return NULL; + + if (vma->vm_ops && vma->vm_ops->open) + vma->vm_ops->open(vma); + + if (vma->vm_file) + get_file(vma->vm_file); + + memcpy(vma_copy, vma, sizeof(*vma)); + + vma_copy->vm_mm = NULL; + vma_copy->vm_next = NULL; + vma_copy->vm_prev = NULL; + + return vma_copy; +} +EXPORT_SYMBOL_GPL(vb2_get_vma); + +/** + * vb2_put_userptr() - release a userspace virtual memory area + * @vma: virtual memory region associated with the area to be released + * + * This function releases the previously acquired memory area after a hardware + * operation. + */ +void vb2_put_vma(struct vm_area_struct *vma) +{ + if (!vma) + return; + + if (vma->vm_ops && vma->vm_ops->close) + vma->vm_ops->close(vma); + + if (vma->vm_file) + fput(vma->vm_file); + + kfree(vma); +} +EXPORT_SYMBOL_GPL(vb2_put_vma); + +/** + * vb2_get_contig_userptr() - lock physically contiguous userspace mapped memory + * @vaddr: starting virtual address of the area to be verified + * @size: size of the area + * @res_paddr: will return physical address for the given vaddr + * @res_vma: will return locked copy of struct vm_area for the given area + * + * This function will go through memory area of size @size mapped at @vaddr and + * verify that the underlying physical pages are contiguous. If they are + * contiguous the virtual memory area is locked and a @res_vma is filled with + * the copy and @res_pa set to the physical address of the buffer. + * + * Returns 0 on success. + */ +int vb2_get_contig_userptr(unsigned long vaddr, unsigned long size, + struct vm_area_struct **res_vma, dma_addr_t *res_pa) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long offset, start, end; + unsigned long this_pfn, prev_pfn; + dma_addr_t pa = 0; + + start = vaddr; + offset = start & ~PAGE_MASK; + end = start + size; + + vma = find_vma(mm, start); + + if (vma == NULL || vma->vm_end < end) + return -EFAULT; + + for (prev_pfn = 0; start < end; start += PAGE_SIZE) { + int ret = follow_pfn(vma, start, &this_pfn); + if (ret) + return ret; + + if (prev_pfn == 0) + pa = this_pfn << PAGE_SHIFT; + else if (this_pfn != prev_pfn + 1) + return -EFAULT; + + prev_pfn = this_pfn; + } + + /* + * Memory is contigous, lock vma and return to the caller + */ + *res_vma = vb2_get_vma(vma); + if (*res_vma == NULL) + return -ENOMEM; + + *res_pa = pa + offset; + return 0; +} +EXPORT_SYMBOL_GPL(vb2_get_contig_userptr); + +/** + * vb2_mmap_pfn_range() - map physical pages to userspace + * @vma: virtual memory region for the mapping + * @paddr: starting physical address of the memory to be mapped + * @size: size of the memory to be mapped + * @vm_ops: vm operations to be assigned to the created area + * @priv: private data to be associated with the area + * + * Returns 0 on success. + */ +int vb2_mmap_pfn_range(struct vm_area_struct *vma, unsigned long paddr, + unsigned long size, + const struct vm_operations_struct *vm_ops, + void *priv) +{ + int ret; + + size = min_t(unsigned long, vma->vm_end - vma->vm_start, size); + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + ret = remap_pfn_range(vma, vma->vm_start, paddr >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (ret) { + printk(KERN_ERR "Remapping memory failed, error: %d\n", ret); + return ret; + } + + vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED; + vma->vm_private_data = priv; + vma->vm_ops = vm_ops; + + vma->vm_ops->open(vma); + + pr_debug("%s: mapped paddr 0x%08lx at 0x%08lx, size %ld\n", + __func__, paddr, vma->vm_start, size); + + return 0; +} +EXPORT_SYMBOL_GPL(vb2_mmap_pfn_range); + +/** + * vb2_common_vm_open() - increase refcount of the vma + * @vma: virtual memory region for the mapping + * + * This function adds another user to the provided vma. It expects + * struct vb2_vmarea_handler pointer in vma->vm_private_data. + */ +static void vb2_common_vm_open(struct vm_area_struct *vma) +{ + struct vb2_vmarea_handler *h = vma->vm_private_data; + + pr_debug("%s: %p, refcount: %d, vma: %08lx-%08lx\n", + __func__, h, atomic_read(h->refcount), vma->vm_start, + vma->vm_end); + + atomic_inc(h->refcount); +} + +/** + * vb2_common_vm_close() - decrease refcount of the vma + * @vma: virtual memory region for the mapping + * + * This function releases the user from the provided vma. It expects + * struct vb2_vmarea_handler pointer in vma->vm_private_data. + */ +static void vb2_common_vm_close(struct vm_area_struct *vma) +{ + struct vb2_vmarea_handler *h = vma->vm_private_data; + + pr_debug("%s: %p, refcount: %d, vma: %08lx-%08lx\n", + __func__, h, atomic_read(h->refcount), vma->vm_start, + vma->vm_end); + + h->put(h->arg); +} + +/** + * vb2_common_vm_ops - common vm_ops used for tracking refcount of mmaped + * video buffers + */ +const struct vm_operations_struct vb2_common_vm_ops = { + .open = vb2_common_vm_open, + .close = vb2_common_vm_close, +}; +EXPORT_SYMBOL_GPL(vb2_common_vm_ops); + +MODULE_DESCRIPTION("common memory handling routines for videobuf2"); +MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/videobuf2-vmalloc.c b/drivers/media/v4l2-core/videobuf2-vmalloc.c new file mode 100644 index 000000000000..94efa04d8d55 --- /dev/null +++ b/drivers/media/v4l2-core/videobuf2-vmalloc.c @@ -0,0 +1,223 @@ +/* + * videobuf2-vmalloc.c - vmalloc memory allocator for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <pawel@osciak.com> + * + * 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. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-vmalloc.h> +#include <media/videobuf2-memops.h> + +struct vb2_vmalloc_buf { + void *vaddr; + struct page **pages; + struct vm_area_struct *vma; + int write; + unsigned long size; + unsigned int n_pages; + atomic_t refcount; + struct vb2_vmarea_handler handler; +}; + +static void vb2_vmalloc_put(void *buf_priv); + +static void *vb2_vmalloc_alloc(void *alloc_ctx, unsigned long size) +{ + struct vb2_vmalloc_buf *buf; + + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return NULL; + + buf->size = size; + buf->vaddr = vmalloc_user(buf->size); + buf->handler.refcount = &buf->refcount; + buf->handler.put = vb2_vmalloc_put; + buf->handler.arg = buf; + + if (!buf->vaddr) { + pr_debug("vmalloc of size %ld failed\n", buf->size); + kfree(buf); + return NULL; + } + + atomic_inc(&buf->refcount); + return buf; +} + +static void vb2_vmalloc_put(void *buf_priv) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + + if (atomic_dec_and_test(&buf->refcount)) { + vfree(buf->vaddr); + kfree(buf); + } +} + +static void *vb2_vmalloc_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + struct vb2_vmalloc_buf *buf; + unsigned long first, last; + int n_pages, offset; + struct vm_area_struct *vma; + dma_addr_t physp; + + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return NULL; + + buf->write = write; + offset = vaddr & ~PAGE_MASK; + buf->size = size; + + + vma = find_vma(current->mm, vaddr); + if (vma && (vma->vm_flags & VM_PFNMAP) && (vma->vm_pgoff)) { + if (vb2_get_contig_userptr(vaddr, size, &vma, &physp)) + goto fail_pages_array_alloc; + buf->vma = vma; + buf->vaddr = ioremap_nocache(physp, size); + if (!buf->vaddr) + goto fail_pages_array_alloc; + } else { + first = vaddr >> PAGE_SHIFT; + last = (vaddr + size - 1) >> PAGE_SHIFT; + buf->n_pages = last - first + 1; + buf->pages = kzalloc(buf->n_pages * sizeof(struct page *), + GFP_KERNEL); + if (!buf->pages) + goto fail_pages_array_alloc; + + /* current->mm->mmap_sem is taken by videobuf2 core */ + n_pages = get_user_pages(current, current->mm, + vaddr & PAGE_MASK, buf->n_pages, + write, 1, /* force */ + buf->pages, NULL); + if (n_pages != buf->n_pages) + goto fail_get_user_pages; + + buf->vaddr = vm_map_ram(buf->pages, buf->n_pages, -1, + PAGE_KERNEL); + if (!buf->vaddr) + goto fail_get_user_pages; + } + + buf->vaddr += offset; + return buf; + +fail_get_user_pages: + pr_debug("get_user_pages requested/got: %d/%d]\n", n_pages, + buf->n_pages); + while (--n_pages >= 0) + put_page(buf->pages[n_pages]); + kfree(buf->pages); + +fail_pages_array_alloc: + kfree(buf); + + return NULL; +} + +static void vb2_vmalloc_put_userptr(void *buf_priv) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + unsigned long vaddr = (unsigned long)buf->vaddr & PAGE_MASK; + unsigned int i; + + if (buf->pages) { + if (vaddr) + vm_unmap_ram((void *)vaddr, buf->n_pages); + for (i = 0; i < buf->n_pages; ++i) { + if (buf->write) + set_page_dirty_lock(buf->pages[i]); + put_page(buf->pages[i]); + } + kfree(buf->pages); + } else { + if (buf->vma) + vb2_put_vma(buf->vma); + iounmap(buf->vaddr); + } + kfree(buf); +} + +static void *vb2_vmalloc_vaddr(void *buf_priv) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + + if (!buf->vaddr) { + pr_err("Address of an unallocated plane requested " + "or cannot map user pointer\n"); + return NULL; + } + + return buf->vaddr; +} + +static unsigned int vb2_vmalloc_num_users(void *buf_priv) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + return atomic_read(&buf->refcount); +} + +static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + int ret; + + if (!buf) { + pr_err("No memory to map\n"); + return -EINVAL; + } + + ret = remap_vmalloc_range(vma, buf->vaddr, 0); + if (ret) { + pr_err("Remapping vmalloc memory, error: %d\n", ret); + return ret; + } + + /* + * Make sure that vm_areas for 2 buffers won't be merged together + */ + vma->vm_flags |= VM_DONTEXPAND; + + /* + * Use common vm_area operations to track buffer refcount. + */ + vma->vm_private_data = &buf->handler; + vma->vm_ops = &vb2_common_vm_ops; + + vma->vm_ops->open(vma); + + return 0; +} + +const struct vb2_mem_ops vb2_vmalloc_memops = { + .alloc = vb2_vmalloc_alloc, + .put = vb2_vmalloc_put, + .get_userptr = vb2_vmalloc_get_userptr, + .put_userptr = vb2_vmalloc_put_userptr, + .vaddr = vb2_vmalloc_vaddr, + .mmap = vb2_vmalloc_mmap, + .num_users = vb2_vmalloc_num_users, +}; +EXPORT_SYMBOL_GPL(vb2_vmalloc_memops); + +MODULE_DESCRIPTION("vmalloc memory handling routines for videobuf2"); +MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>"); +MODULE_LICENSE("GPL"); |