V4L/DVB (7719): pvrusb2: Implement input selection enforcement

In the pvrusb2 driver, different interfaces (e.g. V4L, DVB) have

Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/pvrusb2/pvrusb2-context.c b/drivers/media/video/pvrusb2/pvrusb2-context.c
index a2ce022..b5db6a5 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-context.c
+++ b/drivers/media/video/pvrusb2/pvrusb2-context.c
@@ -245,6 +245,22 @@
 }
 
 
+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 @@
 {
 	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 @@
 }
 
 
+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)
 {
diff --git a/drivers/media/video/pvrusb2/pvrusb2-context.h b/drivers/media/video/pvrusb2/pvrusb2-context.h
index 6fb6ab0..745e270 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-context.h
+++ b/drivers/media/video/pvrusb2/pvrusb2-context.h
@@ -62,6 +62,7 @@
 	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_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(
diff --git a/drivers/media/video/pvrusb2/pvrusb2-dvb.c b/drivers/media/video/pvrusb2/pvrusb2-dvb.c
index c20eef0..2e64f98 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-dvb.c
+++ b/drivers/media/video/pvrusb2/pvrusb2-dvb.c
@@ -244,13 +244,10 @@
 
 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 @@
 {
 	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 @@
 			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 @@
 
 	} 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)
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
index a67dcf8..a3fe251 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
+++ b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
@@ -336,8 +336,10 @@
 	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;
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.c b/drivers/media/video/pvrusb2/pvrusb2-hdw.c
index 63d0af7..72e9056 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-hdw.c
+++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.c
@@ -249,6 +249,7 @@
 };
 
 
+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_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 @@
 	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 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 @@
 			"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_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)
 {
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.h b/drivers/media/video/pvrusb2/pvrusb2-hdw.h
index 74c2503..20295e0 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-hdw.h
+++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.h
@@ -149,6 +149,19 @@
  * 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 *);
 
diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c
index 249d748..b415141 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c
+++ b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c
@@ -57,7 +57,6 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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);