diff mbox

Sparse false-positive warnings

Message ID 20180406234417.2301bcba@vento.lan (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Mauro Carvalho Chehab April 7, 2018, 2:44 a.m. UTC
Em Fri, 6 Apr 2018 14:41:11 -0700
Christopher Li <sparse@chrisli.org> escreveu:

> On Fri, Apr 6, 2018 at 11:59 AM, Mauro Carvalho Chehab
> <mchehab@kernel.org> wrote:
> > drivers/media/pci/ddbridge/ddbridge-core.c:442:9: warning: context imbalance in 'ddb_output_start' - different lock contexts for basic block
> > drivers/media/pci/ddbridge/ddbridge-core.c:457:9: warning: context imbalance in 'ddb_output_stop' - different lock contexts for basic block
> > drivers/media/pci/ddbridge/ddbridge-core.c:472:9: warning: context imbalance in 'ddb_input_stop' - different lock contexts for basic block
> > drivers/media/pci/ddbridge/ddbridge-core.c:504:9: warning: context imbalance in 'ddb_input_start' - different lock contexts for basic block
> >
> > IMO, the problem here is at the sparce logic. Basically, this driver can
> > work with output->dma filled or with NULL. when it is filled, there's a
> > lock to protect the DMA. Otherwise, no lock is needed.  
> 
> There is possible risk there. What if output->dma get changed
> before you try to unlock?

> 
> Of course, from the code  context there is no reason to see output->dma
> can be changed, from developer point of view. However, for sparse,
> there is not way of knowing that.

Hmm... it could try to track the if conditions and check if
"let" statements changing it would be there, but to be frank,
I never studied sparse code (nor did any work with lexical analyzers).

So, I've no idea about how it would track it.

> 
> 
> >
> > Patches like the one below would make sparse to stop complaining:
> >
> > diff --git a/drivers/media/pci/ddbridge/ddbridge-core.c b/drivers/media/pci/ddbridge/ddbridge-core.c
> > index 90687eff5909..69a29d4bf86d 100644
> > --- a/drivers/media/pci/ddbridge/ddbridge-core.c
> > +++ b/drivers/media/pci/ddbridge/ddbridge-core.c
> > @@ -449,15 +449,16 @@ static void ddb_output_stop(struct ddb_output *output)
> >  {
> >         struct ddb *dev = output->port->dev;
> >
> > -       if (output->dma)
> > +       if (output->dma) {
> >                 spin_lock_irq(&output->dma->lock);
> >
> > -       ddbwritel(dev, 0, TS_CONTROL(output));
> > +               ddbwritel(dev, 0, TS_CONTROL(output));
> >
> > -       if (output->dma) {
> >                 ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma));
> >                 output->dma->running = 0;
> >                 spin_unlock_irq(&output->dma->lock);
> > +       } else {
> > +               ddbwritel(dev, 0, TS_CONTROL(output));
> >         }
> >  }  
> 
> if you change that as:
> 
> int locking = output->dma;

Actually output->dma is a pointer, but I got the idea. 

> 
> if (locking)
>         spin_lock_irq();
> 
> ddbwritel();
> 
> if (locking)
>         spin_unlock_irq();
> 
> That will take care of the output->dma changing in between.

I tried that approach on two ways:

1) Doing:

	struct ddb_dma *dma = output->dma;

And replacing all occurrences of output->dma to dma inside the
function (not replacing cause an extra warning from either sparse
or smatch, as it assigns dma then it checks if it is NULL);

2) with the enclosed patch:


Even in this case, where has_dma is const, sparse still generates the
warning. Just in case, I also tried changing has_dma to just
"bool" and just "int".

The warning is still there:

drivers/media/pci/ddbridge/ddbridge-core.c:458:9: warning: context imbalance in 'ddb_output_stop' - different lock contexts for basic block

> 
> > The problem here is that sparse is not considering the conditional code
> > for output->dma.
> >
> > I'd appreciate if you could look into it and add a patch fixing it
> > there, if possible.  
> 
> 
> The current balance checking is simple and dumb.
> There is possibility that using symbolic execution
> to eliminate some of false positive like the above example.
> 
> Unfortunately,  there is not much you can do in the more general
> complicate case.

IMO, it should be a little smarter, in order to at least work on codes
similar to the one produced with the above patch.

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

Comments

Linus Torvalds April 7, 2018, 2:48 a.m. UTC | #1
On Fri, Apr 6, 2018 at 7:44 PM, Mauro Carvalho Chehab
<mchehab@kernel.org> wrote:
>
> -       if (output->dma)
> +       if (has_dma)
>                 spin_lock_irq(&output->dma->lock);
>
>         ddbwritel(dev, 0, TS_CONTROL(output));
>
> -       if (output->dma) {
> +       if (has_dma) {
>                 ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma));
>                 output->dma->running = 0;
>                 spin_unlock_irq(&output->dma->lock);
>
> Even in this case, where has_dma is const, sparse still generates the
> warning. Just in case, I also tried changing has_dma to just
> "bool" and just "int".

Sparse will always warn for this, for the simple reason that the basic
block there in the middle that does that

          ddbwritel(dev, 0, TS_CONTROL(output));

has two different locking contexts.

So the only way to get sparse to not warn is to have something like

    if (output->dma) {
        spin_lock_irq(&output->dma->lock);
        ddbwritel(dev, 0, TS_CONTROL(output));
        ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma));
        output->dma->running = 0;
        spin_unlock_irq(&output->dma->lock);
    } else {
        ddbwritel(dev, 0, TS_CONTROL(output));
    }

which probably generates better code too when that "shared" region is
smaller than the rest of the code.

                     Linus
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mauro Carvalho Chehab April 7, 2018, 3:50 a.m. UTC | #2
Em Fri, 6 Apr 2018 19:48:39 -0700
Linus Torvalds <torvalds@linux-foundation.org> escreveu:

> On Fri, Apr 6, 2018 at 7:44 PM, Mauro Carvalho Chehab
> <mchehab@kernel.org> wrote:
> >
> > -       if (output->dma)
> > +       if (has_dma)
> >                 spin_lock_irq(&output->dma->lock);
> >
> >         ddbwritel(dev, 0, TS_CONTROL(output));
> >
> > -       if (output->dma) {
> > +       if (has_dma) {
> >                 ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma));
> >                 output->dma->running = 0;
> >                 spin_unlock_irq(&output->dma->lock);
> >
> > Even in this case, where has_dma is const, sparse still generates the
> > warning. Just in case, I also tried changing has_dma to just
> > "bool" and just "int".  
> 
> Sparse will always warn for this, for the simple reason that the basic
> block there in the middle that does that
> 
>           ddbwritel(dev, 0, TS_CONTROL(output));

In theory, yes. In practice, however, the device either has dma (and
needs locking) or not.

> has two different locking contexts.
> 
> So the only way to get sparse to not warn is to have something like
> 
>     if (output->dma) {
>         spin_lock_irq(&output->dma->lock);
>         ddbwritel(dev, 0, TS_CONTROL(output));
>         ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma));
>         output->dma->running = 0;
>         spin_unlock_irq(&output->dma->lock);
>     } else {
>         ddbwritel(dev, 0, TS_CONTROL(output));
>     }
> 
> which probably generates better code too when that "shared" region is
> smaller than the rest of the code.

In the specific case of this function, the above is not bad. However,
at the other 3 places where the warnings are generated, the code is
more complex, like on this one:

static void ddb_output_start(struct ddb_output *output)
{
        struct ddb *dev = output->port->dev;
        u32 con = 0x11c, con2 = 0;

        if (output->dma) {
                spin_lock_irq(&output->dma->lock);
                output->dma->cbuf = 0;
                output->dma->coff = 0;
                output->dma->stat = 0;
                ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma));
        }

	if (output->port->input[0]->port->class == DDB_PORT_LOOP)
                con = (1UL << 13) | 0x14;
        else
            	calc_con(output, &con, &con2, 0);

        ddbwritel(dev, 0, TS_CONTROL(output));
        ddbwritel(dev, 2, TS_CONTROL(output));
        ddbwritel(dev, 0, TS_CONTROL(output));
        ddbwritel(dev, con, TS_CONTROL(output));
        ddbwritel(dev, con2, TS_CONTROL2(output));

        if (output->dma) {
                ddbwritel(dev, output->dma->bufval,
                          DMA_BUFFER_SIZE(output->dma));
                ddbwritel(dev, 0, DMA_BUFFER_ACK(output->dma));
                ddbwritel(dev, 1, DMA_BASE_READ);
                ddbwritel(dev, 7, DMA_BUFFER_CONTROL(output->dma));
        }

        ddbwritel(dev, con | 1, TS_CONTROL(output));

        if (output->dma) {
                output->dma->running = 1;
                spin_unlock_irq(&output->dma->lock);
        }
}

duplicating everything doesn't sound right. Ok, we might implement
a __ddb_output_start() function with everything but the lock,
then a new ddb_output_start() with just:

static void ddb_output_start(struct ddb_output *output)
{
        struct ddb *dev = output->port->dev;

        if (output->dma) {
                spin_lock_irq(&output->dma->lock);
		__ddb_output_start(output);
                spin_unlock_irq(&output->dma->lock);
	} else {
		__ddb_output_start(output);
	}
}

for the 4 functions where this warning is produced, but that seems a 
dirty hack made just to shut up the false positive warnings.

Thanks,
Mauro
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Linus Torvalds April 7, 2018, 4:11 a.m. UTC | #3
On Fri, Apr 6, 2018 at 8:50 PM, Mauro Carvalho Chehab
<mchehab@kernel.org> wrote:
>
> duplicating everything doesn't sound right. Ok, we might implement
> a __ddb_output_start() function with everything but the lock,
> then a new ddb_output_start() with just:

Yes. That should avoid the warning and share the code.

And if the shared code is small, inlining will automatically do the
right thing. And if the shared code is large, the small wrapper to get
locking right won't be a big deal.

Of course, you *can* just choose to say that sparse doesn't understand
the code, and ignore the warnings. It's true. But if you are otherwise
sparse-clean, maybe just making sparse happy here too is a good idea.

It does force you to try to think about locking a bit, and it does
tend to make the locking easier to follow even for humans.  Having a
lock taken in one context and then released in some other context can
be confusing even if you aren't sparse.

So it's often just nicer if you see "unconditional lock" + some locked
code + "unconditional unlock" instead of having conditions that you
can get wrong.

It's just that sparse really *requires* that pattern in order to
understand locks.

                Linus
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/media/pci/ddbridge/ddbridge-core.c b/drivers/media/pci/ddbridge/ddbridge-core.c
index 90687eff5909..02646f0c41ff 100644
--- a/drivers/media/pci/ddbridge/ddbridge-core.c
+++ b/drivers/media/pci/ddbridge/ddbridge-core.c
@@ -448,13 +448,14 @@  static void ddb_output_start(struct ddb_output *output)
 static void ddb_output_stop(struct ddb_output *output)
 {
 	struct ddb *dev = output->port->dev;
+	const bool has_dma = output->dma ? true : false;
 
-	if (output->dma)
+	if (has_dma)
 		spin_lock_irq(&output->dma->lock);
 
 	ddbwritel(dev, 0, TS_CONTROL(output));
 
-	if (output->dma) {
+	if (has_dma) {
 		ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma));
 		output->dma->running = 0;
 		spin_unlock_irq(&output->dma->lock);