Re: [RFC PATCH] tuner-core: fix broken G_TUNER call when tuner is in standby

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

 



Em 03-02-2011 21:50, Devin Heitmueller escreveu:
> On Wed, Feb 2, 2011 at 10:21 AM, Mauro Carvalho Chehab
> <mchehab@xxxxxxxxxx> wrote:
>> Em 23-01-2011 20:22, Devin Heitmueller escreveu:
>>> Hello all,
>>>
>>> The following patch addresses a V4L2 specification violation where the
>>> G_TUNER call would return complete garbage in the event that the tuner
>>> is asleep.  Per Hans' suggestion, I have split out the tuner
>>> operational mode from whether it is in standby, and fixed the G_TUNER
>>> call to return as much as possible when the tuner is in standby.
>>>
>>> Without this change, products that have tuners which support standby
>>> mode cannot be tuned from within VLC.
>>>
>>> I recognize that changes to tuner-core tend to be pretty hairy, so I
>>> welcome suggestions/feedback on this patch.
>>>
>>> Regards,
>>>
>>> Devin
>>>
>>> -- Devin J. Heitmueller - Kernel Labs http://www.kernellabs.com
>>>
>>>
>>> tuner_standby_mode.patch
>>>
>>>
>>> tuner-core: fix broken G_TUNER call when tuner is in standby
>>>
>>> From: Devin Heitmueller <dheitmueller@xxxxxxxxxxxxxx>
>>>
>>> The logic for determining the supported device modes was combined with the
>>> logic which dictates whether the tuner was asleep.  This resulted in calls
>>> such as G_TUNER returning complete garbage in the event that the tuner was
>>> in standby mode (a violation of the V4L2 specification, and causing VLC to
>>> be broken for such tuners).
>>>
>>> This patch reworks the logic so the current tuner mode is maintained separately
>>> from whether it is in standby (per Hans Verkuil's suggestion).  It also
>>> restructures the G_TUNER call such that all the staticly defined information
>>> related to the tuner is returned regardless of whether it is in standby (e.g.
>>> the supported frequency range, etc).
>>>
>>> Priority: normal
>>>
>>> Signed-off-by: Devin Heitmueller <dheitmueller@xxxxxxxxxxxxxx>
>>> Cc: Hans Verkuil <hverkuil@xxxxxxxxx>
>>>
>>> --- media_build/linux/drivers/media/video/tuner-core.c        2010-10-24 19:34:59.000000000 -0400
>>> +++ media_build_950qfixes//linux/drivers/media/video/tuner-core.c     2011-01-23 17:18:22.381107568 -0500
>>> @@ -90,6 +90,7 @@
>>>
>>>       unsigned int        mode;
>>>       unsigned int        mode_mask; /* Combination of allowable modes */
>>> +     unsigned int        in_standby:1;
>>>
>>>       unsigned int        type; /* chip type id */
>>>       unsigned int        config;
>>> @@ -700,6 +701,7 @@
>>>  static inline int set_mode(struct i2c_client *client, struct tuner *t, int mode, char *cmd)
>>>  {
>>>       struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops;
>>> +     unsigned int orig_mode = t->mode;
>>>
>>>       if (mode == t->mode)
>>>               return 0;
>>> @@ -709,7 +711,8 @@
>>>       if (check_mode(t, cmd) == -EINVAL) {
>>>               tuner_dbg("Tuner doesn't support this mode. "
>>>                         "Putting tuner to sleep\n");
>>> -             t->mode = T_STANDBY;
>>> +             t->mode = orig_mode;
>>
>> Hmm... as orig_mode = t->mode, this is:
>>        t->mode = t->mode...
>>
>> This doesn't make sense ;)
> 
> Look again.  The target mode being switched to is provided as an
> argument.  So the code preserves the old mode in orig_mode, switches
> to the new mode, then calls check_mode() against the new mode.  If the
> call fails, we reset it back to the mode it was in before the call.

Ok.

>>> +             t->in_standby = 1;
>>>               if (analog_ops->standby)
>>>                       analog_ops->standby(&t->fe);
>>>               return -EINVAL;
>>> @@ -769,7 +772,7 @@
>>>
>>>       if (check_mode(t, "s_power") == -EINVAL)
>>>               return 0;
>>> -     t->mode = T_STANDBY;
>>> +     t->in_standby = 1;
>>>       if (analog_ops->standby)
>>>               analog_ops->standby(&t->fe);
>>>       return 0;
>>> @@ -854,47 +857,54 @@
>>>       struct analog_demod_ops *analog_ops = &t->fe.ops.analog_ops;
>>>       struct dvb_tuner_ops *fe_tuner_ops = &t->fe.ops.tuner_ops;
>>>
>>> -     if (check_mode(t, "g_tuner") == -EINVAL)
>>> -             return 0;
>>
>> Some of those checks are to allow switching between radio and TV
>> mode. Had you test to switch between video/radio mode on your tests
>> (e. g. alternating video streaming on/off with radio tune)?
> 
> I don't have any radio supported devices.

Without testing on devices with radio, you'll likely break tuner-core.
The way radio applications work is different than the video ones. Some
just opens the device, sets radio frequency and closes it (as there's
no need for the radio app to keep the device node opened).

>>>       switch_v4l2();
>>>
>>> +     /* First populate everything that doesn't require talking to the
>>> +        actual hardware */
>>>       vt->type = t->mode;
>>> -     if (analog_ops->get_afc)
>>> -             vt->afc = analog_ops->get_afc(&t->fe);
>>>       if (t->mode == V4L2_TUNER_ANALOG_TV)
>>> +     {
>>>               vt->capability |= V4L2_TUNER_CAP_NORM;
>>> -     if (t->mode != V4L2_TUNER_RADIO) {
>>>               vt->rangelow = tv_range[0] * 16;
>>>               vt->rangehigh = tv_range[1] * 16;
>>> -             return 0;
>>> +     } else {
>>> +             /* radio mode */
>>> +             vt->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
>>> +             vt->capability |= V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
>>> +             vt->audmode = t->audmode;
>>> +             vt->rangelow = radio_range[0] * 16000;
>>> +             vt->rangehigh = radio_range[1] * 16000;
>>>       }
>>>
>>> -     /* radio mode */
>>> -     vt->rxsubchans =
>>> -             V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
>>> -     if (fe_tuner_ops->get_status) {
>>> -             u32 tuner_status;
>>> +     /* If the hardware is in sleep mode, bail out at this point */
>>> +     if (t->in_standby)
>>> +             return 0;
>>
>> This doesn't seem right. Instead, the proper behaviour should be
>> to turn tuner on (likely waiting for some time) and then get a status.
> 
> I disagree.  Turning the tuner on (and incurring firmware load time)
> just to get the tuner's basic capabilities would significantly change
> the timing of the call and likely break applications that expect it to
> return immediately.

This will change the behaviour for all tuners. Changing it 
is not a good idea, as applications may break.

With respect to the firmware load timing, the proper fix would be to honour
the O_NONBLOCK, e. g. if apps open device with O_NONBLOCK, the firmware load
should be asynchronous and ioctl calls should return an error code to indicate
that the operation is still happening. Otherwise, it should be ok to have
a long delay. A fix like that is probably not easy to implement, though.

>>>
>>> -             fe_tuner_ops->get_status(&t->fe, &tuner_status);
>>> -             vt->rxsubchans =
>>> -                     (tuner_status & TUNER_STATUS_STEREO) ?
>>> -                     V4L2_TUNER_SUB_STEREO :
>>> -                     V4L2_TUNER_SUB_MONO;
>>> +     /* Now populate the fields that requires the hardware to be alive */
>>> +     if (t->mode == V4L2_TUNER_ANALOG_TV) {
>>> +             if (analog_ops->get_afc)
>>> +                     vt->afc = analog_ops->get_afc(&t->fe);
>>>       } else {
>>> -             if (analog_ops->is_stereo) {
>>> +             if (fe_tuner_ops->get_status) {
>>
>> Hmm... get_status() taking procedence over is_stereo... not sure if this
>> work as expected. On the other hand:
>>
>> $ git grep is_stereo drivers/media/
>> drivers/media/dvb/dvb-core/dvb_frontend.h:        int  (*is_stereo)(struct dvb_frontend *fe);
>> drivers/media/radio/radio-zoltrix.c:static int zol_is_stereo(struct zoltrix *zol)
>> drivers/media/radio/radio-zoltrix.c:      if (zol_is_stereo(zol))
>> drivers/media/video/msp3400-kthreads.c:   int is_stereo = status & 0x40;
>> drivers/media/video/msp3400-kthreads.c:   if (is_stereo)
>> drivers/media/video/msp3400-kthreads.c:           status, is_stereo, is_bilingual, state->rxsubchans);
>> drivers/media/video/tuner-core.c: if (analog_ops->is_stereo)
>> drivers/media/video/tuner-core.c:                    analog_ops->is_stereo(fe) ? "yes" : "no");
>> drivers/media/video/tuner-core.c:         if (analog_ops->is_stereo) {
>> drivers/media/video/tuner-core.c:                         analog_ops->is_stereo(&t->fe) ?
>>
>> It seems that nobody is using is_stereo() callback anymore. So, we can just
>> get rid of it. This is part of an old API that was there before mkrufky patches
>> that unified analog and digital tuners.
> 
> Feel free to dump deprecated functionality.  I don't know the code
> well enough to make determinations on what code can go away.

Michael knows more about that part, as he wrote the patch that replaced is_stere with get_status,
but from the above grep, it seems that we can just remove is_stereo() from dvb_frontend.h and
from tuner_core.c.
> 
>>> +                     u32 tuner_status;
>>> +
>>> +                     fe_tuner_ops->get_status(&t->fe, &tuner_status);
>>>                       vt->rxsubchans =
>>> -                             analog_ops->is_stereo(&t->fe) ?
>>> +                             (tuner_status & TUNER_STATUS_STEREO) ?
>>>                               V4L2_TUNER_SUB_STEREO :
>>>                               V4L2_TUNER_SUB_MONO;
>>> +             } else {
>>> +                     if (analog_ops->is_stereo) {
>>> +                             vt->rxsubchans =
>>> +                                     analog_ops->is_stereo(&t->fe) ?
>>> +                                     V4L2_TUNER_SUB_STEREO :
>>> +                                     V4L2_TUNER_SUB_MONO;
>>> +                     }
>>>               }
>>> +             if (analog_ops->has_signal)
>>> +                     vt->signal = analog_ops->has_signal(&t->fe);
>>
>> has_signal() seems to be another callback asking for its removal. Only tda8290 has it:
> 
> Yeah, seems I confused "has_signal()" with "get_rf_strength()".  It
> does make me wonder though how you userland is expected to actually
> get the signal strength in for devices that implement
> get_rf_strength().

has_signal() was introduced earlier. get_rf_strength() replaced it, AFAIK. Only
tda8290 is still using the old call. Again, Michael is the right guy for checking,
because he did the changes at the core, and he is maintainin
> 
>> $ git grep has_signal drivers/media/
>> drivers/media/common/tuners/tda8290.c:static int tda8295_has_signal(struct dvb_frontend *fe)
>> drivers/media/common/tuners/tda8290.c:    if (tda8295_has_signal(fe))
>> drivers/media/common/tuners/tda8290.c:static int tda8290_has_signal(struct dvb_frontend *fe)
>> drivers/media/common/tuners/tda8290.c:    .has_signal     = tda8290_has_signal,
>> drivers/media/common/tuners/tda8290.c:    .has_signal     = tda8295_has_signal,
>> drivers/media/dvb/dvb-core/dvb_frontend.h:        int  (*has_signal)(struct dvb_frontend *fe);
>> drivers/media/video/tuner-core.c:static int fe_has_signal(struct dvb_frontend *fe)
>> drivers/media/video/tuner-core.c: .has_signal     = fe_has_signal,
>> drivers/media/video/tuner-core.c: if (analog_ops->has_signal)
>> drivers/media/video/tuner-core.c:                    analog_ops->has_signal(fe));
>> drivers/media/video/tuner-core.c: if (analog_ops->has_signal)
>> drivers/media/video/tuner-core.c:         vt->signal = analog_ops->has_signal(&t->fe);
>>
>> Other drivers use get_rf_strength():
>>
>> $ git grep get_rf_strength drivers/media/
>> drivers/media/common/tuners/tea5761.c:static int tea5761_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
>> drivers/media/common/tuners/tea5761.c:    .get_rf_strength   = tea5761_get_rf_strength,
>> drivers/media/common/tuners/tea5767.c:static int tea5767_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
>> drivers/media/common/tuners/tea5767.c:    .get_rf_strength   = tea5767_get_rf_strength,
>> drivers/media/common/tuners/tuner-simple.c:static int simple_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
>> drivers/media/common/tuners/tuner-simple.c:       .get_rf_strength   = simple_get_rf_strength,
>> drivers/media/common/tuners/tuner-xc2028.c:       .get_rf_strength   = xc2028_signal,
>> drivers/media/dvb/dvb-core/dvb_frontend.h:        int (*get_rf_strength)(struct dvb_frontend *fe, u16 *strength);
>> drivers/media/video/tuner-core.c: if (fe->ops.tuner_ops.get_rf_strength)
>> drivers/media/video/tuner-core.c:         fe->ops.tuner_ops.get_rf_strength(fe, &strength);
>>
>> So, I think we should replace has_signal() at tda8290, with get_rf_strength(),
>> and remove its call from tuner-core.
>>
>>>       }
>>> -     if (analog_ops->has_signal)
>>> -             vt->signal = analog_ops->has_signal(&t->fe);
>>> -     vt->capability |=
>>> -             V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
>>> -     vt->audmode = t->audmode;
>>> -     vt->rangelow = radio_range[0] * 16000;
>>> -     vt->rangehigh = radio_range[1] * 16000;
>>> +
>>>       return 0;
>>>  }
>>>
>>> @@ -911,6 +921,11 @@
>>>       /* do nothing unless we're a radio tuner */
>>>       if (t->mode != V4L2_TUNER_RADIO)
>>>               return 0;
>>> +
>>> +     /* Should this really fail silently if the device is asleep? */
>>> +     if (t->in_standby == 1)
>>> +             return 0;
>>> +
>>
>> Same comment as before: it should wake up the device. That's
>> the expected behaviour by radio applications like console/radio.
>>
>>>       t->audmode = vt->audmode;
>>>       set_radio_freq(client, t->radio_freq);
>>>       return 0;
>>> @@ -1004,14 +1019,11 @@
>>>       *tv = NULL;
>>>
>>>       list_for_each_entry(pos, &tuner_list, list) {
>>> -             int mode_mask;
>>> -
>>>               if (pos->i2c->adapter != adap ||
>>>                   strcmp(pos->i2c->driver->driver.name, "tuner"))
>>>                       continue;
>>>
>>> -             mode_mask = pos->mode_mask & ~T_STANDBY;
>>> -             if (*radio == NULL && mode_mask == T_RADIO)
>>> +             if (*radio == NULL && pos->mode_mask == T_RADIO)
>>>                       *radio = pos;
>>>               /* Note: currently TDA9887 is the only demod-only
>>>                  device. If other devices appear then we need to
>>> @@ -1063,7 +1075,8 @@
>>>                                              t->i2c->addr) >= 0) {
>>>                               t->type = TUNER_TEA5761;
>>>                               t->mode_mask = T_RADIO;
>>> -                             t->mode = T_STANDBY;
>>> +                             t->mode = T_RADIO;
>>> +                             t->in_standby = 1;
>>>                               /* Sets freq to FM range */
>>>                               t->radio_freq = 87.5 * 16000;
>>>                               tuner_lookup(t->i2c->adapter, &radio, &tv);
>>> @@ -1088,7 +1101,8 @@
>>>                               t->type = TUNER_TDA9887;
>>>                               t->mode_mask = T_RADIO | T_ANALOG_TV |
>>>                                              T_DIGITAL_TV;
>>> -                             t->mode = T_STANDBY;
>>> +                             t->mode = T_ANALOG_TV;
>>> +                             t->in_standby = 1;
>>>                               goto register_client;
>>>                       }
>>>                       break;
>>> @@ -1098,7 +1112,8 @@
>>>                                       >= 0) {
>>>                               t->type = TUNER_TEA5767;
>>>                               t->mode_mask = T_RADIO;
>>> -                             t->mode = T_STANDBY;
>>> +                             t->mode = T_RADIO;
>>> +                             t->in_standby = 1;
>>>                               /* Sets freq to FM range */
>>>                               t->radio_freq = 87.5 * 16000;
>>>                               tuner_lookup(t->i2c->adapter, &radio, &tv);
>>
>> Not sure about the above. In the past, T_STANDBY were used at the
>> initial setup to tell the core that the tuner is not in usage. I
>> think we need to do some tests to see if none of the above changes
>> would break it. Complex devices to probe are those with multiple
>> tuners, like devices with a tea5767 for FM and a separate tuner for
>> TV. I think we need to do some tests with those devices.
>>
>> Cheers,
>> Mauro
>>
> 
> This whole driver's an absolute mess, so I was obviously loathe to
> touch *anything* in there. I really didn't have any choice though
> given the violation of the V4L2 specification with regards to the
> G_TUNER call.  Hans was the one who pushed for separating out whether
> the tuner was asleep from the tuner mode, which unfortunately is
> necessary because G_TUNER is returning a garbage value for the type
> field (because it's returning T_STANDBY, which is a violation of the
> spec).

check_mode() should be doing such changes. It is there to ensure that
the driver is on the right mode when tuner is called.

One of the major problems that tuner-core tries to address is the
switch between the different modes (radio, analog, digital?).

Btw, I just checked that we still have some V4L1 mess inside it.
I'll write a few cleanup patches and test with some devices here.

> I don't think I have any devices with radio support, so I cannot test
> those scenarios.
> 
> I'm not confident any get call should really be waking up the tuner
> (which could be very expensive if firmware loading is involved).  It
> seems like the driver should return what it knows, and the other
> fields which require the tuner to be alive should just return zero.
> This should be fine since we're really only talking about signal
> strength, which would already be zero if the device was in standby.

I see your point, but why some application would try to access a
power-down tuner, if it doesn't have the intention to power it up?

> I welcome any alternative patch which makes G_TUNER work per the spec
> for tuners in a standby state.

I'll try to write a patch for it.

Cheers,
Mauro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux