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>
        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;
        }
 }
 
+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;
        sd->contrast = CONTRAST_DEF;
        sd->colors = COLOR_DEF;
        sd->autogain = AUTOGAIN_DEF;
+       sd->ag_cnt = -1;
        return 0;
 }
 
        else
                Et_init2(gspca_dev);
 
+       setautogain(gspca_dev);
+
        reg_w_val(gspca_dev, ET_RESET_ALL, 0x08);
        et_video(gspca_dev, 1);         /* video on */
 }
 {
 }
 
-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;
 #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;
                        __u8 *data,                     /* isoc packet */
                        int len)                        /* iso packet length */
 {
-       struct sd *sd;
        int seqframe;
 
        seqframe = data[0] & 0x3f;
                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) {
        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;
 }
 
        .stop0 = sd_stop0,
        .close = sd_close,
        .pkt_scan = sd_pkt_scan,
+       .dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */
 
 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;
        sd->contrast = CONTRAST_DEF;
        sd->colors = COLOR_DEF;
        sd->autogain = AUTOGAIN_DEF;
+       sd->ag_cnt = -1;
        return 0;
 }
 
        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)
 {
 
 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);
        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) {
        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)
        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 ||
 
                        /* 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);
                                }
                        }
 
        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;
 }
 
        .stop0 = sd_stop0,
        .close = sd_close,
        .pkt_scan = sd_pkt_scan,
+       .dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */
 
 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;
        sd->contrast = CONTRAST_DEF;
        sd->colors = COLOR_DEF;
        sd->autogain = AUTOGAIN_DEF;
+       sd->ag_cnt = -1;
+
        return 0;
 }
 
        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)
 {
        reg_w1(gspca_dev, 0x01, reg1);
        setbrightness(gspca_dev);
        setcontrast(gspca_dev);
+       setautogain(gspca_dev);
 }
 
 static void sd_stopN(struct gspca_dev *gspca_dev)
 {
 }
 
-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) {
                        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)
        }
 }
 
+/* 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 */
                                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 */
 /* 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) {
        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;
 }
 
        .stop0 = sd_stop0,
        .close = sd_close,
        .pkt_scan = sd_pkt_scan,
+       .dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */
 
        }
 }
 
+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;
                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: */
        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);
                        __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) {
        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;
 }
 
        .stop0 = sd_stop0,
        .close = sd_close,
        .pkt_scan = sd_pkt_scan,
+       .dq_callback = do_autogain,
 };
 
 /* -- module initialisation -- */