V4L/DVB (8604): gspca: Fix of "scheduling while atomic" crash.

The crash is due to USB exchanges done at interrupt level.
These exchanges, tied to autogain, are now done by the application.
Also, there is a fix about autogain start.
Concerned subdrivers: etoms, pac7311, sonixj and spca561.

Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/gspca/etoms.c b/drivers/media/video/gspca/etoms.c
index 6a4e682..1dbe92d 100644
--- a/drivers/media/video/gspca/etoms.c
+++ b/drivers/media/video/gspca/etoms.c
@@ -461,6 +461,52 @@
 	reg_w_val(gspca_dev, 0x80, 0x20);	/* 0x20; */
 }
 
+static void setbrightness(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	__u8 brightness = sd->brightness;
+
+	for (i = 0; i < 4; i++)
+		reg_w_val(gspca_dev, ET_O_RED + i, brightness);
+}
+
+static void getbrightness(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	int brightness = 0;
+
+	for (i = 0; i < 4; i++) {
+		reg_r(gspca_dev, ET_O_RED + i, 1);
+		brightness += gspca_dev->usb_buf[0];
+	}
+	sd->brightness = brightness >> 3;
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 RGBG[] = { 0x80, 0x80, 0x80, 0x80, 0x00, 0x00 };
+	__u8 contrast = sd->contrast;
+
+	memset(RGBG, contrast, sizeof(RGBG) - 2);
+	reg_w(gspca_dev, ET_G_RED, RGBG, 6);
+}
+
+static void getcontrast(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	int contrast = 0;
+
+	for (i = 0; i < 4; i++) {
+		reg_r(gspca_dev, ET_G_RED + i, 1);
+		contrast += gspca_dev->usb_buf[0];
+	}
+	sd->contrast = contrast >> 2;
+}
+
 static void setcolors(struct gspca_dev *gspca_dev)
 {
 	struct sd *sd = (struct sd *) gspca_dev;
@@ -492,6 +538,16 @@
 	}
 }
 
+static void setautogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->autogain)
+		sd->ag_cnt = AG_CNT_START;
+	else
+		sd->ag_cnt = -1;
+}
+
 static void Et_init1(struct gspca_dev *gspca_dev)
 {
 	__u8 value;
@@ -614,6 +670,7 @@
 	sd->contrast = CONTRAST_DEF;
 	sd->colors = COLOR_DEF;
 	sd->autogain = AUTOGAIN_DEF;
+	sd->ag_cnt = -1;
 	return 0;
 }
 
@@ -641,6 +698,8 @@
 	else
 		Et_init2(gspca_dev);
 
+	setautogain(gspca_dev);
+
 	reg_w_val(gspca_dev, ET_RESET_ALL, 0x08);
 	et_video(gspca_dev, 1);		/* video on */
 }
@@ -658,52 +717,6 @@
 {
 }
 
-static void setbrightness(struct gspca_dev *gspca_dev)
-{
-	struct sd *sd = (struct sd *) gspca_dev;
-	int i;
-	__u8 brightness = sd->brightness;
-
-	for (i = 0; i < 4; i++)
-		reg_w_val(gspca_dev, ET_O_RED + i, brightness);
-}
-
-static void getbrightness(struct gspca_dev *gspca_dev)
-{
-	struct sd *sd = (struct sd *) gspca_dev;
-	int i;
-	int brightness = 0;
-
-	for (i = 0; i < 4; i++) {
-		reg_r(gspca_dev, ET_O_RED + i, 1);
-		brightness += gspca_dev->usb_buf[0];
-	}
-	sd->brightness = brightness >> 3;
-}
-
-static void setcontrast(struct gspca_dev *gspca_dev)
-{
-	struct sd *sd = (struct sd *) gspca_dev;
-	__u8 RGBG[] = { 0x80, 0x80, 0x80, 0x80, 0x00, 0x00 };
-	__u8 contrast = sd->contrast;
-
-	memset(RGBG, contrast, sizeof(RGBG) - 2);
-	reg_w(gspca_dev, ET_G_RED, RGBG, 6);
-}
-
-static void getcontrast(struct gspca_dev *gspca_dev)
-{
-	struct sd *sd = (struct sd *) gspca_dev;
-	int i;
-	int contrast = 0;
-
-	for (i = 0; i < 4; i++) {
-		reg_r(gspca_dev, ET_G_RED + i, 1);
-		contrast += gspca_dev->usb_buf[0];
-	}
-	sd->contrast = contrast >> 2;
-}
-
 static __u8 Et_getgainG(struct gspca_dev *gspca_dev)
 {
 	struct sd *sd = (struct sd *) gspca_dev;
@@ -733,15 +746,22 @@
 #define LIMIT(color) \
 	(unsigned char)((color > 0xff)?0xff:((color < 0)?0:color))
 
-static void setautogain(struct gspca_dev *gspca_dev)
+static void do_autogain(struct gspca_dev *gspca_dev)
 {
-	__u8 luma = 0;
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 luma;
 	__u8 luma_mean = 128;
 	__u8 luma_delta = 20;
 	__u8 spring = 4;
-	int Gbright = 0;
+	int Gbright;
 	__u8 r, g, b;
 
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt >= 0)
+		return;
+	sd->ag_cnt = AG_CNT_START;
+
 	Gbright = Et_getgainG(gspca_dev);
 	reg_r(gspca_dev, ET_LUMA_CENTER, 4);
 	g = (gspca_dev->usb_buf[0] + gspca_dev->usb_buf[3]) >> 1;
@@ -768,7 +788,6 @@
 			__u8 *data,			/* isoc packet */
 			int len)			/* iso packet length */
 {
-	struct sd *sd;
 	int seqframe;
 
 	seqframe = data[0] & 0x3f;
@@ -783,13 +802,6 @@
 		frame = gspca_frame_add(gspca_dev, LAST_PACKET, frame,
 					data, 0);
 		gspca_frame_add(gspca_dev, FIRST_PACKET, frame, data, len);
-		sd = (struct sd *) gspca_dev;
-		if (sd->ag_cnt >= 0) {
-			if (--sd->ag_cnt < 0) {
-				sd->ag_cnt = AG_CNT_START;
-				setautogain(gspca_dev);
-			}
-		}
 		return;
 	}
 	if (len) {
@@ -862,10 +874,8 @@
 	struct sd *sd = (struct sd *) gspca_dev;
 
 	sd->autogain = val;
-	if (val)
-		sd->ag_cnt = AG_CNT_START;
-	else
-		sd->ag_cnt = -1;
+	if (gspca_dev->streaming)
+		setautogain(gspca_dev);
 	return 0;
 }
 
@@ -889,6 +899,7 @@
 	.stop0 = sd_stop0,
 	.close = sd_close,
 	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */
diff --git a/drivers/media/video/gspca/pac7311.c b/drivers/media/video/gspca/pac7311.c
index ea3d702..815bea6 100644
--- a/drivers/media/video/gspca/pac7311.c
+++ b/drivers/media/video/gspca/pac7311.c
@@ -31,7 +31,9 @@
 struct sd {
 	struct gspca_dev gspca_dev;		/* !! must be the first item */
 
-	int avg_lum;
+	int lum_sum;
+	atomic_t avg_lum;
+	atomic_t do_gain;
 
 	unsigned char brightness;
 	unsigned char contrast;
@@ -271,6 +273,7 @@
 	sd->contrast = CONTRAST_DEF;
 	sd->colors = COLOR_DEF;
 	sd->autogain = AUTOGAIN_DEF;
+	sd->ag_cnt = -1;
 	return 0;
 }
 
@@ -311,6 +314,18 @@
 	PDEBUG(D_CONF|D_STREAM, "color: %i", sd->colors);
 }
 
+static void setautogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->autogain) {
+		sd->lum_sum = 0;
+		sd->ag_cnt = AG_CNT_START;
+	} else {
+		sd->ag_cnt = -1;
+	}
+}
+
 /* this function is called at open time */
 static int sd_open(struct gspca_dev *gspca_dev)
 {
@@ -320,8 +335,6 @@
 
 static void sd_start(struct gspca_dev *gspca_dev)
 {
-	struct sd *sd = (struct sd *) gspca_dev;
-
 	reg_w(gspca_dev, 0xff, 0x01);
 	reg_w_buf(gspca_dev, 0x0002, "\x48\x0a\x40\x08\x00\x00\x08\x00", 8);
 	reg_w_buf(gspca_dev, 0x000a, "\x06\xff\x11\xff\x5a\x30\x90\x4c", 8);
@@ -394,6 +407,7 @@
 	setcontrast(gspca_dev);
 	setbrightness(gspca_dev);
 	setcolors(gspca_dev);
+	setautogain(gspca_dev);
 
 	/* set correct resolution */
 	switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
@@ -431,13 +445,6 @@
 	reg_w(gspca_dev, 0xff, 0x01);
 	reg_w(gspca_dev, 0x78, 0x04);
 	reg_w(gspca_dev, 0x78, 0x05);
-
-	if (sd->autogain) {
-		sd->ag_cnt = AG_CNT_START;
-		sd->avg_lum = 0;
-	} else {
-		sd->ag_cnt = -1;
-	}
 }
 
 static void sd_stopN(struct gspca_dev *gspca_dev)
@@ -473,13 +480,20 @@
 	reg_w(gspca_dev, 0x78, 0x44); /* Bit_0=start stream, Bit_7=LED */
 }
 
-static void setautogain(struct gspca_dev *gspca_dev, int luma)
+static void do_autogain(struct gspca_dev *gspca_dev)
 {
+	struct sd *sd = (struct sd *) gspca_dev;
+	int luma;
 	int luma_mean = 128;
 	int luma_delta = 20;
 	__u8 spring = 5;
 	int Gbright;
 
+	if (!atomic_read(&sd->do_gain))
+		return;
+	atomic_set(&sd->do_gain, 0);
+
+	luma = atomic_read(&sd->avg_lum);
 	Gbright = reg_r(gspca_dev, 0x02);
 	PDEBUG(D_FRAM, "luma mean %d", luma);
 	if (luma < luma_mean - luma_delta ||
@@ -523,12 +537,13 @@
 
 			/* start of frame */
 			if (sd->ag_cnt >= 0 && p > 28) {
-				sd->avg_lum += data[p - 23];
+				sd->lum_sum += data[p - 23];
 				if (--sd->ag_cnt < 0) {
 					sd->ag_cnt = AG_CNT_START;
-					setautogain(gspca_dev,
-						sd->avg_lum / AG_CNT_START);
-					sd->avg_lum = 0;
+					atomic_set(&sd->avg_lum,
+						sd->lum_sum / AG_CNT_START);
+					sd->lum_sum = 0;
+					atomic_set(&sd->do_gain, 1);
 				}
 			}
 
@@ -677,12 +692,8 @@
 	struct sd *sd = (struct sd *) gspca_dev;
 
 	sd->autogain = val;
-	if (val) {
-		sd->ag_cnt = AG_CNT_START;
-		sd->avg_lum = 0;
-	} else {
-		sd->ag_cnt = -1;
-	}
+	if (gspca_dev->streaming)
+		setautogain(gspca_dev);
 	return 0;
 }
 
@@ -706,6 +717,7 @@
 	.stop0 = sd_stop0,
 	.close = sd_close,
 	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */
diff --git a/drivers/media/video/gspca/sonixj.c b/drivers/media/video/gspca/sonixj.c
index b60ff60..245a30e 100644
--- a/drivers/media/video/gspca/sonixj.c
+++ b/drivers/media/video/gspca/sonixj.c
@@ -32,7 +32,7 @@
 struct sd {
 	struct gspca_dev gspca_dev;	/* !! must be the first item */
 
-	int avg_lum;
+	atomic_t avg_lum;
 	unsigned int exposure;
 
 	unsigned short brightness;
@@ -781,6 +781,8 @@
 	sd->contrast = CONTRAST_DEF;
 	sd->colors = COLOR_DEF;
 	sd->autogain = AUTOGAIN_DEF;
+	sd->ag_cnt = -1;
+
 	return 0;
 }
 
@@ -946,6 +948,22 @@
 	reg_w1(gspca_dev, 0x05, data);
 }
 
+static void setautogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+	case SENSOR_MO4000:
+	case SENSOR_MI0360:
+		if (sd->autogain)
+			sd->ag_cnt = AG_CNT_START;
+		else
+			sd->ag_cnt = -1;
+		break;
+	}
+}
+
 /* -- start the camera -- */
 static void sd_start(struct gspca_dev *gspca_dev)
 {
@@ -1078,6 +1096,7 @@
 	reg_w1(gspca_dev, 0x01, reg1);
 	setbrightness(gspca_dev);
 	setcontrast(gspca_dev);
+	setautogain(gspca_dev);
 }
 
 static void sd_stopN(struct gspca_dev *gspca_dev)
@@ -1124,16 +1143,23 @@
 {
 }
 
-static void setautogain(struct gspca_dev *gspca_dev)
+static void do_autogain(struct gspca_dev *gspca_dev)
 {
 	struct sd *sd = (struct sd *) gspca_dev;
-	/* Thanks S., without your advice, autobright should not work :) */
 	int delta;
-	int expotimes = 0;
+	int expotimes;
 	__u8 luma_mean = 130;
 	__u8 luma_delta = 20;
 
-	delta = sd->avg_lum;
+	/* Thanks S., without your advice, autobright should not work :) */
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt >= 0)
+		return;
+	sd->ag_cnt = AG_CNT_START;
+
+	delta = atomic_read(&sd->avg_lum);
+	PDEBUG(D_FRAM, "mean lum %d", delta);
 	if (delta < luma_mean - luma_delta ||
 	    delta > luma_mean + luma_delta) {
 		switch (sd->sensor) {
@@ -1145,8 +1171,9 @@
 			sd->exposure = setexposure(gspca_dev,
 					(unsigned int) (expotimes << 8));
 			break;
-		case SENSOR_MO4000:
-		case SENSOR_MI0360:
+		default:
+/*		case SENSOR_MO4000: */
+/*		case SENSOR_MI0360: */
 			expotimes = sd->exposure;
 			expotimes += (luma_mean - delta) >> 6;
 			if (expotimes < 0)
@@ -1159,6 +1186,8 @@
 	}
 }
 
+/* scan the URB packets */
+/* This function is run at interrupt level. */
 static void sd_pkt_scan(struct gspca_dev *gspca_dev,
 			struct gspca_frame *frame,	/* target */
 			__u8 *data,			/* isoc packet */
@@ -1175,9 +1204,6 @@
 				frame, data, sof + 2);
 		if (sd->ag_cnt < 0)
 			return;
-		if (--sd->ag_cnt >= 0)
-			return;
-		sd->ag_cnt = AG_CNT_START;
 /* w1 w2 w3 */
 /* w4 w5 w6 */
 /* w7 w8 */
@@ -1192,9 +1218,7 @@
 /* w5 */
 		avg_lum += ((data[sof + 31] << 8) | data[sof + 32]) >> 4;
 		avg_lum >>= 4;
-		sd->avg_lum = avg_lum;
-		PDEBUG(D_PACK, "mean lum %d", avg_lum);
-		setautogain(gspca_dev);
+		atomic_set(&sd->avg_lum, avg_lum);
 		return;
 	}
 	if (gspca_dev->last_packet_type == LAST_PACKET) {
@@ -1321,10 +1345,8 @@
 	struct sd *sd = (struct sd *) gspca_dev;
 
 	sd->autogain = val;
-	if (val)
-		sd->ag_cnt = AG_CNT_START;
-	else
-		sd->ag_cnt = -1;
+	if (gspca_dev->streaming)
+		setautogain(gspca_dev);
 	return 0;
 }
 
@@ -1348,6 +1370,7 @@
 	.stop0 = sd_stop0,
 	.close = sd_close,
 	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */
diff --git a/drivers/media/video/gspca/spca561.c b/drivers/media/video/gspca/spca561.c
index a261745..1073ac3 100644
--- a/drivers/media/video/gspca/spca561.c
+++ b/drivers/media/video/gspca/spca561.c
@@ -644,6 +644,18 @@
 	}
 }
 
+static void setautogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->chip_revision == Rev072A) {
+		if (sd->autogain)
+			sd->ag_cnt = AG_CNT_START;
+		else
+			sd->ag_cnt = -1;
+	}
+}
+
 static void sd_start(struct gspca_dev *gspca_dev)
 {
 	struct sd *sd = (struct sd *) gspca_dev;
@@ -671,6 +683,7 @@
 		reg_w_val(dev, 0x8500, mode);	/* mode */
 		reg_w_val(dev, 0x8700, Clck);	/* 0x27 clock */
 		reg_w_val(dev, 0x8112, 0x10 | 0x20);
+		setautogain(gspca_dev);
 		break;
 	default:
 /*	case Rev012A: */
@@ -720,18 +733,24 @@
 	reg_w_val(gspca_dev->dev, 0x8114, 0);
 }
 
-static void setautogain(struct gspca_dev *gspca_dev)
+static void do_autogain(struct gspca_dev *gspca_dev)
 {
 	struct sd *sd = (struct sd *) gspca_dev;
-	int expotimes = 0;
-	int pixelclk = 0;
-	int gainG = 0;
+	int expotimes;
+	int pixelclk;
+	int gainG;
 	__u8 R, Gr, Gb, B;
 	int y;
 	__u8 luma_mean = 110;
 	__u8 luma_delta = 20;
 	__u8 spring = 4;
 
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt >= 0)
+		return;
+	sd->ag_cnt = AG_CNT_START;
+
 	switch (sd->chip_revision) {
 	case Rev072A:
 		reg_r(gspca_dev, 0x8621, 1);
@@ -795,18 +814,10 @@
 			__u8 *data,		/* isoc packet */
 			int len)		/* iso packet length */
 {
-	struct sd *sd = (struct sd *) gspca_dev;
-
 	switch (data[0]) {
 	case 0:		/* start of frame */
 		frame = gspca_frame_add(gspca_dev, LAST_PACKET, frame,
 					data, 0);
-		if (sd->ag_cnt >= 0) {
-			if (--sd->ag_cnt < 0) {
-				sd->ag_cnt = AG_CNT_START;
-				setautogain(gspca_dev);
-			}
-		}
 		data += SPCA561_OFFSET_DATA;
 		len -= SPCA561_OFFSET_DATA;
 		if (data[1] & 0x10) {
@@ -944,10 +955,8 @@
 	struct sd *sd = (struct sd *) gspca_dev;
 
 	sd->autogain = val;
-	if (val)
-		sd->ag_cnt = AG_CNT_START;
-	else
-		sd->ag_cnt = -1;
+	if (gspca_dev->streaming)
+		setautogain(gspca_dev);
 	return 0;
 }
 
@@ -971,6 +980,7 @@
 	.stop0 = sd_stop0,
 	.close = sd_close,
 	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */