Re: [PATCH] uvcvideo: do not use usb_set_interface on bulk EP

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Am 17.02.2014 10:28, schrieb Laurent Pinchart:
> Hi Oleksij,
> 
> On Monday 17 February 2014 10:05:41 Oleksij Rempel wrote:
>> Am 17.02.2014 01:26, schrieb Laurent Pinchart:
>>> On Sunday 16 February 2014 10:59:32 Oleksij Rempel wrote:
>>>> Current uvcvideo driver do not correctly disables stream on
>>>> Bulk VideoStream EP. This couse problems on a webcam buildin
>>>> to Asus Zenbook UX302LA. For example it will be not disabled
>>>> and can't be strated after usb port was suspended.
>>>
>>> s/srated/started/
>>>
>>>> Since usb_set_interface can be used only on Iso EP. We need some
>>>> way to handle Bulk VS EP.
>>>> Widnwos (xp - 8) uvcvideo driver use usb_clear_halt
>>>
>>> s/Widnwos/Windows/
>>>
>>>> in this case. It seems to be correct way to hadle Bulk and Int EPs.
>>>
>>> s/hadle/handle/
>>>
>>> Do you mind if I fix the typos and grammar when applying the patch to my
>>> tree ?
>>
>> It will be great! Thank you :)
>>
>>>> And it solves this problem on UX302LA ultrabook.
>>>>
>>>> CC: stable@xxxxxxxxxxxxxxx
>>>> Signed-off-by: Oleksij Rempel <linux@xxxxxxxxxxxxxxxx>
>>>> ---
>>>>
>>>>  drivers/media/usb/uvc/uvc_video.c | 10 +++++++++-
>>>>  1 file changed, 9 insertions(+), 1 deletion(-)
>>>
>>> I've copied the content of your other e-mail below to keep all discussions
>>> in one place.
>>>
>>>> Hello all,
>>>>
>>>> i have Asus UX302LA with buildin uvc webcam: 1bcf:2987 Sunplus
>>>> Innovation Technology Inc.
>>>
>>> Could you please send me the lsusb -v output for that camera ?
>>
>> Suddenly lsusb can't completely decode this descriptor.
> 
> The UVC-specific interface descriptors should come before the endpoint 
> descriptor. The uvcvideo driver supports both orders, lsusb doesn't seem to. 
> Patches will be appreciated :-)
> 
>>>> This cam uses bulk stream and has some troubles with current linux uvc
>>>> driver. It is driver bug since it works on plain uvcvideo windows driver
>>>> (even XP SP2 :)).
>>>>
>>>> The symptom is fallowing:
>>>> - the cam will work first time with all linux apps
>>>> - after stream off it will take about 4 seconds untill webcam LED will
>>>> go off too. The time is equal to usbautosuspend settings.
>>>> - it will be impossible to start webcam second time. Even no reboot to
>>>> windows will help. Only complete power off will restore functionality.
>>>> - if i disable asbautosuspend, webcam will work, but LED will never go
>>>> off even if it is not used.
>>>>
>>>> It looks like VS interface is not turned off on Linux and in combination
>>>> with usb autosuspend, webcam or usb link will go to nirvana.
>>>
>>> I agree with your analysis. Your camera seems to never stop the stream and
>>> to crash when suspended while the stream is active.
>>>
>>>> After comparing usb log from windows and linux i found that this webcam
>>>> need CLEAR HALT of VideoStreaming interface. Linux driver will do only
>>>> SET INTERFACE command. Suddenly i don't have other bulk webcams to see
>>>> if windows do it with all of them.
>>>>
>>>> Here is comparison of different webcams and operations which do windows8
>>>> on stream end:
>>>> Asus UX302LA (UVC 1.00; Bulk) - 1bcf:2987 Sunplus Innovation Technology
>>>> Inc. • CLEAR HALT (Interface=0x1) VS interface
>>>
>>> The UVC specification doesn't mention clearing the streaming interface
>>> control endpoint halt status. The specification actually doesn't state
>>> how the host should control stream start/stop for devices that use bulk
>>> endpoints. That's a shortcoming of UVC in my opinion
>>
>> Are there any thing it specification uvc 1.0? I found only 1.1 and 1.5.
> 
> There's a UVC 1.0a.

Do you have it? can you please send it me?

>> Another documentation from Cypress, "AN75779 How to Implement an Image
>> Sensor Interface Using EZ-USB ® FX3TM in a USB Video Class (UVC)
>> Framework", paragraph "5.9 Terminating the Video Streaming":
>>
>> "When the application closes, it issues a clear feature request on a
>> Windows platform or a Set Interface with alternate setting = 0 request
>> on a Mac platform."
>>
>> It confirms your assumption that there is no clear way to handle bulk
>> stream.
> 
> So I assume your camera won't work on Mac OS.

I think Apple don't care about it :)

>>> I don't know whether Microsoft has decided to address this issue in their
>>> own way by sending a CLEAR_FEATURE(HALT) request when stopping the video
>>> stream, or if that's a standard procedure in their UVC driver that hasn't
>>> been added specifically for bulk endpoints. Neither the UVC specification
>>> nor the USB specification forbid sending a CLEAR_FEATURE(HALT) request to
>>> the streaming interface bulk endpoint in this case.
>>>
>>> It should be noted that the USB specification allows devices to stall a
>>> SET_INTERFACE(0) request for an interface that has a single alternate
>>> setting (section 9.4.10). In that case the Linux USB core sends a
>>> CLEAR_FEATURE(HALT) request to every endpoint of that interface.
>>
>> I tested this webcam with Windows Xp and 8. It was handled in same way:
>> only CLEAR_FEATURE(HALT) was send.
>>
>>>> Logitech C905: (UVC - 1.00; ISO)
>>>> • SET INTERFACE (Interface=1)
>>>> • CLEAR HALT (Interface=0x7) Halt interrupt EP
>>>
>>> The CLEAR_FEATURE(HALT) request is targeted at endpoints, not interfaces.
>>> I suppose you mean endpoint 0x87 here (EP 7 IN) ?
>>
>> yes
>>
>>> Similarly, with your bulk
>>> webcam, to which endpoint is the CLEAR_FEATURE(HALT) request sent ?
>>
>> EP 1 IN (0x81)
>>
>>>> Logitech C600: (UVC - 1.00; ISO)
>>>> • SET INTERFACE (Interface=1)
>>>> • CLEAR HALT (Interface=0x7) Halt interrupt EP
>>>>
>>>> Logitech E3500: (UVC - 1.00; ISO)
>>>> • CLEAR HALT (Interface=0x7) Halt interrupt EP
>>>> • SET INTERFACE (Interface=1) (different order then in c600 and c905 )
>>>>
>>>> Logitech C920: (UVC 1.00; ISO)
>>>> • SET INTERFACE (Interface=1)
>>>> • Interrupt is EP 3, why it is not halted?
>>>>
>>>> Asus UX31A (UVC - 1.00; ISO) - 04f2:b330 Chicony Electronics Co., Ltd
>>>> Asus 720p CMOS webcam
>>>> • SET INTERFACE (Interface=1)
>>>> • Interrupt is EP 3, why it is not halted?
>>>>
>>>> So far it looks like Windows8 use CLEAR HALT for Bulk and SET INTERFACE
>>>> for Iso.
>>>
>>> What about Windows XP and Windows 7 ? Do they use the same scheme ?
>>
>> Windows XP uses only SET INTERFACE on this webcam. Windows 7 not tested
>> for now - should i?
> 
> If Windows XP and Windows 8 behave the same way I suppose we can skip Windows 
> 7.
> 
>>>> But i don't understand why Windows8 trying to halt Interrupt if EP=7 and
>>>> not doing that if EP=3?
>>>
>>> No idea... I wonder if the Windows UVC driver includes device quirks.
>>
>> Yes, Windows 8 knows more about this webcam's then i would like to. For
>> example it uses intensively Logitech specific XUs (without installing
>> Logitech software): Right Light registers; some think what looks like
>> watch dog. May be it knows how to disable LED :) NSA friendly system...
> 
> Nice... If you've reversed-engineered RightLight information would be welcome 
> :-)

That was easy:
https://wikidevi.com/wiki/82066163-7050-ab49-b8cc-b3855e8d2252
See attachment with a testprogram.

>>>> diff --git a/drivers/media/usb/uvc/uvc_video.c
>>>> b/drivers/media/usb/uvc/uvc_video.c index 898c208..9d335c0 100644
>>>> --- a/drivers/media/usb/uvc/uvc_video.c
>>>> +++ b/drivers/media/usb/uvc/uvc_video.c
>>>> @@ -1847,7 +1847,15 @@ int uvc_video_enable(struct uvc_streaming *stream,
>>>> int enable)
>>>>
>>>>  	if (!enable) {
>>>>  		uvc_uninit_video(stream, 1);
>>>> -		usb_set_interface(stream->dev->udev, stream->intfnum, 0);
>>>> +		if (stream->intf->num_altsetting > 1) {
>>>> +			usb_set_interface(stream->dev->udev,
>>>> +					  stream->intfnum, 0);
>>>> +		} else {
>>>> +			usb_clear_halt(stream->dev->udev,
>>>> +				usb_rcvctrlpipe(stream->dev->udev,
>>>> +						stream->intfnum));
>>>
>>> usb_rcvctrlpipe() expects an endpoint number as its second argument, not
>>> an interface number. The following call should do.
>>>
>>>         usb_clear_halt(stream->dev->udev,
>>>         stream->header.bEndpointAddress);
>>>
>>> We probably need to validate the streaming header bEndpointAddress field
>>> when probing the device to ensure that we won't call usb_clear_halt() on
>>> an unrelated endpoint.
>>>
>>>> +		}
>>>> +
>>>
>>> This might cause a regression with other bulk devices that expect a
>>> SET_INTERFACE(0), or don't expect a CLEAR_FEATURE(HALT), although I
>>> suppose those devices wouldn't work with Windows then. I've tested you
>>> patch (with the above modification) with the only bulk device I own and it
>>> doesn't seem to cause any regression.
>>
>> I assume, my cam was produced after XP uvc driver. So it should be
>> default behaviour.
> 
> What would you think about calling both usb_set_interface() and 
> usb_clear_halt() for bulk-based devices ? Something like
> 
> -		usb_set_interface(stream->dev->udev, stream->intfnum, 0);
> +		if (stream->intf->num_altsetting == 1)
> +			usb_clear_halt(stream->dev->udev,
> +				       stream->header.bEndpointAddress);
> 

It works too. I was just afraid to be too different form MS
implementation. Manufactures usually test it against MS driver so it
sort of dictate specification.

-- 
Regards,
Oleksij
/*
 * XU example for uvcvideo driver.
 * All parameters based on /usr/share/uvcdynctrl/data/046d/logitech.xml
 * and tested with Logitech, Inc. QuickCam Pro for Notebooks (046d:0991)
 * Use it on your own risk, it can damage you device.
 *
 * Copyright (C) 2012
 * Oleksij Rempel <bug-track@xxxxxxxxxxxxxxxxx>
 *
 * 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.
 *
 * Description:
 * Most Logitech Cams support so called "RightLight Technology"
 * It is software based multi field mesuring and adopted exposure
 * settings.
 * The thecnology works as fallow:
 * the image on the cam will splitted to 8 fields, each field has two parts.
 *  -----------------  To get the light intensity of one field,
 *  |   |   |   |   |  host should SET the adress of field and then 
 *  |  c4   |  c5   |  GET the result. The result has two bytes,
 *  -----------------  first byte is the left part of the field,
 *  |   |   |   |   |  sesond byte is the right part of the field.
 *  |  c6   |  c7   |  
 *  -----------------
 *  |   |   |   |   |
 *  |  c8   |  c9   |
 *  -----------------
 *  |   |   |   |   |
 *  |  ca   |  cb   |
 *  -----------------
 */

#include <unistd.h>
#include <stdint.h>
#include <linux/uvcvideo.h>
#include <linux/videodev2.h>
#include <linux/usb/video.h>
#include <assert.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define DEVVIDEO "/dev/video0"

#define EXPOSURE_STEP 5
#define MIN_EXPOSURE 50
#define MAX_EXPOSURE 300
#define UVC_UID_LOGITECH_USER_HW_CONTROL 10

/* This is the GUID of XU repsoinble for mesuring
 * 82066163-7050-ab49-b8cc-b3855e8d2252.
 * currently not used. */
#define UVC_XU_GUID \
  {0x82, 0x06, 0x61, 0x63, 0x70, 0x50, 0xab, 0x49, \
   0xb8, 0xcc, 0xb3, 0x85, 0x5e, 0x8d, 0x22, 0x52}

/* With CS 5 we set the adress of field
 * On CS 6 we get results.
 */
#define XU_CS_MESURE_FIELD 5
#define XU_CS_MESURE_FIELD_RET 6

/* for some unknown reason we need to send two bytes:
 * filed number and 0x02.*/
#define MESURE_FIELDS \

const uint16_t logitech_field_2x4[8] = {
  0x02c4, 0x02c5,
  0x02c6, 0x02c7,
  0x02c8, 0x02c9,
  0x02ca, 0x02cb};

const uint16_t logitech_field_2x2[4] = {
  0x02c6, 0x02c7,
  0x02c8, 0x02c9,};

struct dyn_exposure {
  int configured;
  const uint16_t *fields;
  unsigned int fields_count;
  unsigned int width;
  unsigned int width_offset;
  unsigned int height;
  uint8_t results[16];
  unsigned int result_size;
  unsigned int result_width;
  int exp_state;
  int fd;
  struct uvc_xu_control_query xu_set;
  struct uvc_xu_control_query xu_get;
};

static int xioctl(int fd, int request, void *arg) {

  int ret = ioctl(fd, request, arg);
  if (ret == -1) {
    /* check the errno, it can be standart ioctl error or
     * errno provided by uvcvideo. For possible errors see:
     * linux/drivers/media/video/uvc/uvc_ctrl.c: uvc_xu_ctrl_query
     * and linux/include/asm-generic/errno.h */
    ret =  errno;
    printf("filed with errno: %d\n", ret);
  }
  return ret;
}

static void set_exposure(int fd, int exp) {
  struct v4l2_control ctrl;

  /* check if we use manual exposure
   * if not set it. 
   */
  ctrl.id = V4L2_CID_EXPOSURE_AUTO;
  ctrl.value = 0;
  xioctl(fd, VIDIOC_G_CTRL, &ctrl);
  if (ctrl.value != 1) {
    ctrl.value = 1;
    xioctl(fd, VIDIOC_S_CTRL, &ctrl);
    printf ("Setting manual exposure\n");
  }

  ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
  ctrl.value = 0; 
  xioctl(fd, VIDIOC_G_CTRL, &ctrl);
  printf (" --- %03u\n", ctrl.value);
  /* */
  if (exp < 0) {
    ctrl.value -= EXPOSURE_STEP;
    if (ctrl.value < MIN_EXPOSURE)
      ctrl.value = MIN_EXPOSURE;
    xioctl(fd, VIDIOC_S_CTRL, &ctrl);
  } else if (exp > 0) {
    ctrl.value += EXPOSURE_STEP;
    if (ctrl.value < MAX_EXPOSURE)
      ctrl.value = MAX_EXPOSURE;
    xioctl(fd, VIDIOC_S_CTRL, &ctrl);
  }
}

static void config_loigitech_4x4 (struct dyn_exposure *priv)
{
  priv->fields = (const uint16_t *)&logitech_field_2x4;
  priv->fields_count = sizeof(logitech_field_2x4)/sizeof(uint16_t);
  priv->width = 4;
  priv->width_offset = 0;
  priv->height = 4;
  priv->result_size = priv->fields_count * 2;
  priv->result_width = priv->result_size / priv->height;
}

static void config_loigitech_4x2 (struct dyn_exposure *priv)
{
  priv->fields = (const uint16_t *)&logitech_field_2x2;
  priv->fields_count = sizeof(logitech_field_2x2)/sizeof(uint16_t);
  priv->width = 4;
  priv->width_offset = 0;
  priv->height = 2;
  priv->result_size = priv->fields_count * 2;
  priv->result_width = priv->result_size / priv->height;
}

static void config_loigitech_2x2 (struct dyn_exposure *priv)
{
  priv->fields = (const uint16_t *)&logitech_field_2x2;
  priv->fields_count = sizeof(logitech_field_2x2)/sizeof(uint16_t);
  priv->width = 2;
  priv->width_offset = 1;
  priv->height = 2;
  priv->result_size = priv->fields_count * 2;
  priv->result_width = priv->result_size / priv->height;
}


static void init_xu(struct dyn_exposure *priv)
{
  /* prepare set_ struct */
  priv->xu_set.unit = UVC_UID_LOGITECH_USER_HW_CONTROL;
  priv->xu_set.selector = XU_CS_MESURE_FIELD;
  priv->xu_set.query = UVC_SET_CUR;
  priv->xu_set.size = 2;

  /* prepare get struct */
  priv->xu_get.unit = UVC_UID_LOGITECH_USER_HW_CONTROL;
  priv->xu_get.selector = XU_CS_MESURE_FIELD_RET;
  priv->xu_get.query = UVC_GET_CUR;
  priv->xu_get.size = 2;

}

static void get_xu_fileds(struct dyn_exposure *priv)
{
  int i8, i16; /* 8 - fileds, 16 - mesure squers */
  i16 = 0;
  for (i8 = 0; i8 < 8; i8++)
  {
    priv->xu_set.data = (unsigned char*)&priv->fields[i8];
    xioctl(priv->fd, UVCIOC_CTRL_QUERY, (void *)&priv->xu_set);
    
    priv->xu_get.data = (unsigned char*)&priv->results[i16];
    xioctl(priv->fd, UVCIOC_CTRL_QUERY, (void *)&priv->xu_get);
    i16 += 2;
  }
}

/* Easy and naiv precessor.
 */
static void process_fields (struct dyn_exposure *priv)
{
  int r;
  unsigned int a, b, c;
  unsigned long int avarage;
  a = r = 0;
  avarage = 0;

  while (a < priv->result_size)
  {
    b = a + priv->width_offset;
    c = b + priv->width;
    while (b < c)
    {
      if (priv->results[b] < 0x30) {
        r -= 1; // underexposed
      } else if (priv->results[b] > 0x60)
        r += 2; // over exposed
      else
        r += 1; // ok

      avarage += priv->results[b];

      printf("| (%02u)%03u ", b, priv->results[b]);
      b++;
    }
    printf("|\n");
    a += priv->result_width;
  }

  if (r > 18) {
    printf ("over exposed\n");
    set_exposure (priv->fd, 1);
  } else if (r < 10) {
    printf ("underexposed \n");
    set_exposure (priv->fd, -1);
  } else
    printf ("ok\n");
}


int main(void) {
  int fd = open(DEVVIDEO, O_RDWR);
  assert(fd > 0);

  struct dyn_exposure priv;
  priv.fd = fd;
  init_xu (&priv);
  // we can choise differnt configuration
  // to mesure light.
  config_loigitech_4x4(&priv);
  //config_loigitech_2x4(&priv);
  //config_loigitech_2x2(&priv);
  for(;;)
  {
    get_xu_fileds (&priv); 
    process_fields(&priv);
    usleep(500000);
  }

  close(fd);
  return 0;
}

Attachment: signature.asc
Description: OpenPGP digital signature


[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]