V4L/DVB (7719): pvrusb2: Implement input selection enforcement
authorMike Isely <isely@pobox.com>
Mon, 21 Apr 2008 06:47:43 +0000 (03:47 -0300)
committerMauro Carvalho Chehab <mchehab@infradead.org>
Thu, 24 Apr 2008 17:09:49 +0000 (14:09 -0300)
In the pvrusb2 driver, different interfaces (e.g. V4L, DVB) have

Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
drivers/media/video/pvrusb2/pvrusb2-context.c
drivers/media/video/pvrusb2/pvrusb2-context.h
drivers/media/video/pvrusb2/pvrusb2-dvb.c
drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
drivers/media/video/pvrusb2/pvrusb2-hdw.c
drivers/media/video/pvrusb2/pvrusb2-hdw.h
drivers/media/video/pvrusb2/pvrusb2-v4l2.c

index a2ce022c515a97dcb0fd13c817ced8551672f4e0..b5db6a5bab31e94b5338feb1b788ee14b129eb6e 100644 (file)
@@ -245,6 +245,22 @@ struct pvr2_context *pvr2_context_create(
 }
 
 
+static void pvr2_context_reset_input_limits(struct pvr2_context *mp)
+{
+       unsigned int tmsk,mmsk;
+       struct pvr2_channel *cp;
+       struct pvr2_hdw *hdw = mp->hdw;
+       mmsk = pvr2_hdw_get_input_available(hdw);
+       tmsk = mmsk;
+       for (cp = mp->mc_first; cp; cp = cp->mc_next) {
+               if (!cp->input_mask) continue;
+               tmsk &= cp->input_mask;
+       }
+       pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk);
+       pvr2_hdw_commit_ctl(hdw);
+}
+
+
 static void pvr2_context_enter(struct pvr2_context *mp)
 {
        mutex_lock(&mp->mutex);
@@ -300,7 +316,9 @@ void pvr2_channel_done(struct pvr2_channel *cp)
 {
        struct pvr2_context *mp = cp->mc_head;
        pvr2_context_enter(mp);
+       cp->input_mask = 0;
        pvr2_channel_disclaim_stream(cp);
+       pvr2_context_reset_input_limits(mp);
        if (cp->mc_next) {
                cp->mc_next->mc_prev = cp->mc_prev;
        } else {
@@ -316,6 +334,57 @@ void pvr2_channel_done(struct pvr2_channel *cp)
 }
 
 
+int pvr2_channel_limit_inputs(struct pvr2_channel *cp,unsigned int cmsk)
+{
+       unsigned int tmsk,mmsk;
+       int ret = 0;
+       struct pvr2_channel *p2;
+       struct pvr2_hdw *hdw = cp->hdw;
+
+       mmsk = pvr2_hdw_get_input_available(hdw);
+       cmsk &= mmsk;
+       if (cmsk == cp->input_mask) {
+               /* No change; nothing to do */
+               return 0;
+       }
+
+       pvr2_context_enter(cp->mc_head);
+       do {
+               if (!cmsk) {
+                       cp->input_mask = 0;
+                       pvr2_context_reset_input_limits(cp->mc_head);
+                       break;
+               }
+               tmsk = mmsk;
+               for (p2 = cp->mc_head->mc_first; p2; p2 = p2->mc_next) {
+                       if (p2 == cp) continue;
+                       if (!p2->input_mask) continue;
+                       tmsk &= p2->input_mask;
+               }
+               if (!(tmsk & cmsk)) {
+                       ret = -EPERM;
+                       break;
+               }
+               tmsk &= cmsk;
+               if ((ret = pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk)) != 0) {
+                       /* Internal failure changing allowed list; probably
+                          should not happen, but react if it does. */
+                       break;
+               }
+               cp->input_mask = cmsk;
+               pvr2_hdw_commit_ctl(hdw);
+       } while (0);
+       pvr2_context_exit(cp->mc_head);
+       return ret;
+}
+
+
+unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *cp)
+{
+       return cp->input_mask;
+}
+
+
 int pvr2_channel_claim_stream(struct pvr2_channel *cp,
                              struct pvr2_context_stream *sp)
 {
index 6fb6ab02285128799e265f553e4e76c63240cfb7..745e270233c26572f2c8f647ae64b43b24908268 100644 (file)
@@ -62,6 +62,7 @@ struct pvr2_channel {
        struct pvr2_channel *mc_prev;
        struct pvr2_context_stream *stream;
        struct pvr2_hdw *hdw;
+       unsigned int input_mask;
        void (*check_func)(struct pvr2_channel *);
 };
 
@@ -72,6 +73,8 @@ void pvr2_context_disconnect(struct pvr2_context *);
 
 void pvr2_channel_init(struct pvr2_channel *,struct pvr2_context *);
 void pvr2_channel_done(struct pvr2_channel *);
+int pvr2_channel_limit_inputs(struct pvr2_channel *,unsigned int);
+unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *);
 int pvr2_channel_claim_stream(struct pvr2_channel *,
                              struct pvr2_context_stream *);
 struct pvr2_ioread *pvr2_channel_create_mpeg_stream(
index c20eef0f077e42b51787c4a879f10a83db7dce82..2e64f98d124149404ba76d6f5ab3131e14910662 100644 (file)
@@ -244,13 +244,10 @@ static int pvr2_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
 
 static int pvr2_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
 {
-       /* TO DO: This function will call into the core and request for
-        * input to be set to 'dtv' if (acquire) and if it isn't set already.
-        *
-        * If (!acquire) then we should do nothing -- don't switch inputs
-        * again unless the analog side of the driver requests the bus.
-        */
-       return 0;
+       struct pvr2_dvb_adapter *adap = fe->dvb->priv;
+       return pvr2_channel_limit_inputs(
+           &adap->channel,
+           (acquire ? (1 << PVR2_CVAL_INPUT_DTV) : 0));
 }
 
 static int pvr2_dvb_adapter_init(struct pvr2_dvb_adapter *adap)
@@ -320,32 +317,26 @@ static int pvr2_dvb_frontend_init(struct pvr2_dvb_adapter *adap)
 {
        struct pvr2_hdw *hdw = adap->channel.hdw;
        struct pvr2_dvb_props *dvb_props = hdw->hdw_desc->dvb_props;
-       int ret;
+       int ret = 0;
 
        if (dvb_props == NULL) {
                err("fe_props not defined!");
                return -EINVAL;
        }
 
-       /* FIXME: This code should be moved into the core,
-        * and should only be called if we don't already have
-        * control of the bus.
-        *
-        * We can't call "pvr2_dvb_bus_ctrl(adap->fe, 1)" from here,
-        * because adap->fe isn't defined yet.
-        */
-       ret = pvr2_ctrl_set_value(pvr2_hdw_get_ctrl_by_id(hdw,
-                                                         PVR2_CID_INPUT),
-                                 PVR2_CVAL_INPUT_DTV);
-       if (ret != 0)
+       ret = pvr2_channel_limit_inputs(
+           &adap->channel,
+           (1 << PVR2_CVAL_INPUT_DTV));
+       if (ret) {
+               err("failed to grab control of dtv input (code=%d)",
+                   ret);
                return ret;
-
-       pvr2_hdw_commit_ctl(hdw);
-
+       }
 
        if (dvb_props->frontend_attach == NULL) {
                err("frontend_attach not defined!");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto done;
        }
 
        if ((dvb_props->frontend_attach(adap) == 0) && (adap->fe)) {
@@ -354,7 +345,8 @@ static int pvr2_dvb_frontend_init(struct pvr2_dvb_adapter *adap)
                        err("frontend registration failed!");
                        dvb_frontend_detach(adap->fe);
                        adap->fe = NULL;
-                       return -ENODEV;
+                       ret = -ENODEV;
+                       goto done;
                }
 
                if (dvb_props->tuner_attach)
@@ -368,10 +360,13 @@ static int pvr2_dvb_frontend_init(struct pvr2_dvb_adapter *adap)
 
        } else {
                err("no frontend was attached!");
-               return -ENODEV;
+               ret = -ENODEV;
+               return ret;
        }
 
-       return 0;
+ done:
+       pvr2_channel_limit_inputs(&adap->channel, 0);
+       return ret;
 }
 
 static int pvr2_dvb_frontend_exit(struct pvr2_dvb_adapter *adap)
index a67dcf84b596a498e27475802c59edc03af9cdd0..a3fe251d6fd912eeaefc9f07157b702074d67abd 100644 (file)
@@ -336,8 +336,10 @@ struct pvr2_hdw {
        int v4l_minor_number_vbi;
        int v4l_minor_number_radio;
 
-       /* Bit mask of PVR2_CVAL_INPUT choices which are valid */
+       /* Bit mask of PVR2_CVAL_INPUT choices which are valid for the hardware */
        unsigned int input_avail_mask;
+       /* Bit mask of PVR2_CVAL_INPUT choices which are currenly allowed */
+       unsigned int input_allowed_mask;
 
        /* Location of eeprom or a negative number if none */
        int eeprom_addr;
index 63d0af759ed8646053973a9fdf5f331bcf81286d..72e9056557bda0b7a12b20459cb417dae25bfbc4 100644 (file)
@@ -249,6 +249,7 @@ static const struct pvr2_fx2cmd_descdef pvr2_fx2cmd_desc[] = {
 };
 
 
+static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v);
 static void pvr2_hdw_state_sched(struct pvr2_hdw *);
 static int pvr2_hdw_state_eval(struct pvr2_hdw *);
 static void pvr2_hdw_set_cur_freq(struct pvr2_hdw *,unsigned long);
@@ -404,30 +405,12 @@ static int ctrl_get_input(struct pvr2_ctrl *cptr,int *vp)
 
 static int ctrl_check_input(struct pvr2_ctrl *cptr,int v)
 {
-       return ((1 << v) & cptr->hdw->input_avail_mask) != 0;
+       return ((1 << v) & cptr->hdw->input_allowed_mask) != 0;
 }
 
 static int ctrl_set_input(struct pvr2_ctrl *cptr,int m,int v)
 {
-       struct pvr2_hdw *hdw = cptr->hdw;
-
-       if (hdw->input_val != v) {
-               hdw->input_val = v;
-               hdw->input_dirty = !0;
-       }
-
-       /* Handle side effects - if we switch to a mode that needs the RF
-          tuner, then select the right frequency choice as well and mark
-          it dirty. */
-       if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
-               hdw->freqSelector = 0;
-               hdw->freqDirty = !0;
-       } else if ((hdw->input_val == PVR2_CVAL_INPUT_TV) ||
-                  (hdw->input_val == PVR2_CVAL_INPUT_DTV)) {
-               hdw->freqSelector = 1;
-               hdw->freqDirty = !0;
-       }
-       return 0;
+       return pvr2_hdw_set_input(cptr->hdw,v);
 }
 
 static int ctrl_isdirty_input(struct pvr2_ctrl *cptr)
@@ -1916,6 +1899,7 @@ struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf,
        if (hdw_desc->flag_has_composite) m |= 1 << PVR2_CVAL_INPUT_COMPOSITE;
        if (hdw_desc->flag_has_fmradio) m |= 1 << PVR2_CVAL_INPUT_RADIO;
        hdw->input_avail_mask = m;
+       hdw->input_allowed_mask = hdw->input_avail_mask;
 
        /* If not a hybrid device, pathway_state never changes.  So
           initialize it here to what it should forever be. */
@@ -3948,6 +3932,24 @@ static int pvr2_hdw_state_update(struct pvr2_hdw *hdw)
 }
 
 
+static unsigned int print_input_mask(unsigned int msk,
+                                    char *buf,unsigned int acnt)
+{
+       unsigned int idx,ccnt;
+       unsigned int tcnt = 0;
+       for (idx = 0; idx < ARRAY_SIZE(control_values_input); idx++) {
+               if (!((1 << idx) & msk)) continue;
+               ccnt = scnprintf(buf+tcnt,
+                                acnt-tcnt,
+                                "%s%s",
+                                (tcnt ? ", " : ""),
+                                control_values_input[idx]);
+               tcnt += ccnt;
+       }
+       return tcnt;
+}
+
+
 static const char *pvr2_pathway_state_name(int id)
 {
        switch (id) {
@@ -4016,6 +4018,28 @@ static unsigned int pvr2_hdw_report_unlocked(struct pvr2_hdw *hdw,int which,
                        "state: %s",
                        pvr2_get_state_name(hdw->master_state));
        case 4: {
+               unsigned int tcnt = 0;
+               unsigned int ccnt;
+
+               ccnt = scnprintf(buf,
+                                acnt,
+                                "Hardware supported inputs: ");
+               tcnt += ccnt;
+               tcnt += print_input_mask(hdw->input_avail_mask,
+                                        buf+tcnt,
+                                        acnt-tcnt);
+               if (hdw->input_avail_mask != hdw->input_allowed_mask) {
+                       ccnt = scnprintf(buf+tcnt,
+                                        acnt-tcnt,
+                                        "; allowed inputs: ");
+                       tcnt += ccnt;
+                       tcnt += print_input_mask(hdw->input_allowed_mask,
+                                                buf+tcnt,
+                                                acnt-tcnt);
+               }
+               return tcnt;
+       }
+       case 5: {
                struct pvr2_stream_stats stats;
                if (!hdw->vid_stream) break;
                pvr2_stream_get_stats(hdw->vid_stream,
@@ -4210,6 +4234,74 @@ unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *hdw)
 }
 
 
+unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *hdw)
+{
+       return hdw->input_allowed_mask;
+}
+
+
+static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v)
+{
+       if (hdw->input_val != v) {
+               hdw->input_val = v;
+               hdw->input_dirty = !0;
+       }
+
+       /* Handle side effects - if we switch to a mode that needs the RF
+          tuner, then select the right frequency choice as well and mark
+          it dirty. */
+       if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+               hdw->freqSelector = 0;
+               hdw->freqDirty = !0;
+       } else if ((hdw->input_val == PVR2_CVAL_INPUT_TV) ||
+                  (hdw->input_val == PVR2_CVAL_INPUT_DTV)) {
+               hdw->freqSelector = 1;
+               hdw->freqDirty = !0;
+       }
+       return 0;
+}
+
+
+int pvr2_hdw_set_input_allowed(struct pvr2_hdw *hdw,
+                              unsigned int change_mask,
+                              unsigned int change_val)
+{
+       int ret = 0;
+       unsigned int nv,m,idx;
+       LOCK_TAKE(hdw->big_lock);
+       do {
+               nv = hdw->input_allowed_mask & ~change_mask;
+               nv |= (change_val & change_mask);
+               nv &= hdw->input_avail_mask;
+               if (!nv) {
+                       /* No legal modes left; return error instead. */
+                       ret = -EPERM;
+                       break;
+               }
+               hdw->input_allowed_mask = nv;
+               if ((1 << hdw->input_val) & hdw->input_allowed_mask) {
+                       /* Current mode is still in the allowed mask, so
+                          we're done. */
+                       break;
+               }
+               /* Select and switch to a mode that is still in the allowed
+                  mask */
+               if (!hdw->input_allowed_mask) {
+                       /* Nothing legal; give up */
+                       break;
+               }
+               m = hdw->input_allowed_mask;
+               for (idx = 0; idx < (sizeof(m) << 3); idx++) {
+                       if (!((1 << idx) & m)) continue;
+                       pvr2_hdw_set_input(hdw,idx);
+                       break;
+               }
+       } while (0);
+       LOCK_GIVE(hdw->big_lock);
+       return ret;
+}
+
+
 /* Find I2C address of eeprom */
 static int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw)
 {
index 74c2503f8e3f6c6a3618d940fb85a546c2613224..20295e0c19954efe6e3784a781aa0c6c445551ac 100644 (file)
@@ -149,6 +149,19 @@ int pvr2_hdw_commit_ctl(struct pvr2_hdw *);
  * will be according to PVR_CVAL_INPUT_xxxx definitions. */
 unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *);
 
+/* Return a bit mask of allowed input selections for this device.  Mask bits
+ * will be according to PVR_CVAL_INPUT_xxxx definitions. */
+unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *);
+
+/* Change the set of allowed input selections for this device.  Both
+   change_mask and change_valu are mask bits according to
+   PVR_CVAL_INPUT_xxxx definitions.  The change_mask parameter indicate
+   which settings are being changed and the change_val parameter indicates
+   whether corresponding settings are being set or cleared. */
+int pvr2_hdw_set_input_allowed(struct pvr2_hdw *,
+                              unsigned int change_mask,
+                              unsigned int change_val);
+
 /* Return name for this driver instance */
 const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *);
 
index 249d7488e482de4fe24da8907bc3e5b4436b6b52..b415141b28595bf6d02a16263b7c0054fd710e63 100644 (file)
@@ -57,7 +57,6 @@ struct pvr2_v4l2_fh {
        struct pvr2_v4l2_fh *vprev;
        wait_queue_head_t wait_data;
        int fw_mode_flag;
-       int prev_input_val;
 };
 
 struct pvr2_v4l2 {
@@ -900,20 +899,6 @@ static int pvr2_v4l2_release(struct inode *inode, struct file *file)
        v4l2_prio_close(&vp->prio, &fhp->prio);
        file->private_data = NULL;
 
-       /* Restore the previous input selection, if it makes sense
-          to do so. */
-       if (fhp->dev_info->v4l_type == VFL_TYPE_RADIO) {
-               struct pvr2_ctrl *cp;
-               int pval;
-               cp = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT);
-               pvr2_ctrl_get_value(cp,&pval);
-               /* Only restore if we're still selecting the radio */
-               if (pval == PVR2_CVAL_INPUT_RADIO) {
-                       pvr2_ctrl_set_value(cp,fhp->prev_input_val);
-                       pvr2_hdw_commit_ctl(hdw);
-               }
-       }
-
        if (fhp->vnext) {
                fhp->vnext->vprev = fhp->vprev;
        } else {
@@ -944,6 +929,8 @@ static int pvr2_v4l2_open(struct inode *inode, struct file *file)
        struct pvr2_v4l2_fh *fhp;
        struct pvr2_v4l2 *vp;
        struct pvr2_hdw *hdw;
+       unsigned int input_mask = 0;
+       int ret = 0;
 
        dip = container_of(video_devdata(file),struct pvr2_v4l2_dev,devbase);
 
@@ -969,6 +956,29 @@ static int pvr2_v4l2_open(struct inode *inode, struct file *file)
        pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_v4l2_fh id=%p",fhp);
        pvr2_channel_init(&fhp->channel,vp->channel.mc_head);
 
+       if (dip->v4l_type == VFL_TYPE_RADIO) {
+               /* Opening device as a radio, legal input selection subset
+                  is just the radio. */
+               input_mask = (1 << PVR2_CVAL_INPUT_RADIO);
+       } else {
+               /* Opening the main V4L device, legal input selection
+                  subset includes all analog inputs. */
+               input_mask = ((1 << PVR2_CVAL_INPUT_RADIO) |
+                             (1 << PVR2_CVAL_INPUT_TV) |
+                             (1 << PVR2_CVAL_INPUT_COMPOSITE) |
+                             (1 << PVR2_CVAL_INPUT_SVIDEO));
+       }
+       ret = pvr2_channel_limit_inputs(&fhp->channel,input_mask);
+       if (ret) {
+               pvr2_channel_done(&fhp->channel);
+               pvr2_trace(PVR2_TRACE_STRUCT,
+                          "Destroying pvr_v4l2_fh id=%p (input mask error)",
+                          fhp);
+
+               kfree(fhp);
+               return ret;
+       }
+
        fhp->vnext = NULL;
        fhp->vprev = vp->vlast;
        if (vp->vlast) {
@@ -979,18 +989,6 @@ static int pvr2_v4l2_open(struct inode *inode, struct file *file)
        vp->vlast = fhp;
        fhp->vhead = vp;
 
-       /* Opening the /dev/radioX device implies a mode switch.
-          So execute that here.  Note that you can get the
-          IDENTICAL effect merely by opening the normal video
-          device and setting the input appropriately. */
-       if (dip->v4l_type == VFL_TYPE_RADIO) {
-               struct pvr2_ctrl *cp;
-               cp = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT);
-               pvr2_ctrl_get_value(cp,&fhp->prev_input_val);
-               pvr2_ctrl_set_value(cp,PVR2_CVAL_INPUT_RADIO);
-               pvr2_hdw_commit_ctl(hdw);
-       }
-
        fhp->file = file;
        file->private_data = fhp;
        v4l2_prio_open(&vp->prio,&fhp->prio);