From: "Hans J. Koch" <hjk@hansjkoch.de>
To: Vitalii Demianets <vitas@nppfactor.kiev.ua>
Cc: "Hans J. Koch" <hjk@hansjkoch.de>, Cong Ding <dinggnu@gmail.com>,
linux-kernel@vger.kernel.org,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Subject: Re: [PATCH v2] drivers/uio/uio_pdrv_genirq.c: Fix memory freeing issues
Date: Sat, 8 Dec 2012 00:47:21 +0100 [thread overview]
Message-ID: <20121207234720.GB3786@local> (raw)
In-Reply-To: <201212071700.54367.vitas@nppfactor.kiev.ua>
On Fri, Dec 07, 2012 at 05:00:54PM +0200, Vitalii Demianets wrote:
> >
> > On second thought, we can't call enable_irq()/disable_irq() unconditionally
> > because of the potential disable counter (irq_desc->depth) disbalance.
> > That's why we need UIO_IRQ_DISABLED flag, and that's why we should check it
> > in uio_pdrv_genirq_irqcontrol().
> > On the other hand, you are right in that we don't need to check it inside
> > irq handler. Inside irq handler we can disable irq and set the flag
> > unconditionally, because:
> > a) We know for sure that irqs are enabled, because we are inside
> > (not-shared) irq handler;
> > and
> > b) We are guarded from potential race conditions by spin_lock_irqsave()
> > call in uio_pdrv_genirq_irqcontrol().
> >
> > So,yes, we can get rid of costly atomic call to
> > test_and_set_bit(UIO_IRQ_DISABLED,..) inside irq handler. But I still don't
> > like the idea of mixing this optimization with bug fixes in a single patch.
>
> On the third thought, we can't ;)
> Imagine the SMP system where uio_pdrv_genirq_irqcontrol() is being executed on
> CPU0 and irq handler is running concurrently on CPU1. To protect from
> disable_irq counter disbalance we must first check current irq status, and in
> atomic manner. Thus we prevent double-disable, one from
> uio_pdrv_genirq_irqcontrol() running on CPU0 and another form irq handler
> running on CPU1.
> Above consideration justifies current code.
>
> But it seems we have potential concurrency problem here anyway. Here is
> theoretical scenario:
> 1) Initial state: irq is enabled, uio_pdrv_genirq_irqcontrol() starts being
> executed on CPU0 with irq_on=1 and at the same time, concurrently, irq
> handler starts being executed on CPU1;
> 2) irq handler executes line
> if (!test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
> as irq was enabled, the condition holds. And now UIO_IRQ_DISABLED is set.
> 3) uio_pdrv_genirq_irqcontrol() executes line
> if (test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags))
> as UIO_IRQ_DISABLED was set by CPU1, the condition holds. And now
> UIO_IRQ_DISABLED is cleared.
> 4) CPU0 executes enable_irq() . IRQ is enabled. "Unbalanced enable for
> IRQ %d\n" warning is issued.
> 5) irq handler on CPU1 executes disable_irq_nosync(). IRQ is disabled.
>
> And now we are in situation where IRQ is disabled, but UIO_IRQ_DISABLED is
> cleared. Bad.
>
> The above scenario is purely theoretical, I have no means to check it in
> hardware.
> The (theoretical) solution is to guard UIO_IRQ_DISABLED test and actual irq
> disabling inside irq handler by priv->lock.
>
> What do you think about it? Is it worth worrying about?
Hi Vitalii,
thanks a lot for analyzing the problem so thoroughly. It made me review
uio_pdrv_genirq.c again, and I noticed several issues and came to the
following conclusions:
1.) priv->lock is completely unnecessary. It is only used in one function,
so there's nothing it could possibly protect.
2.) All these "test_and_clear_bit" and "test_and_set_bit" calls are also
unnecessary. We can simply use enable_irq and disable_irq in both the
irq handler and in uio_pdrv_genirq_irqcontrol.
We should go "back to the roots" and have a look at how UIO works.
The workflow it is intended for is like this:
1.) Hardware is in Reset State (e.g. after boot). Any decent hardware
has its interrupt disabled at that time.
2.) uio_pdrv_genirq is loaded. Kernel enables the irq.
3.) Userspace part of the driver comes up. It will initialize the hardware
(including setting the bits that enable the interrupt).
4.) Userspace will then issue a blocking read() on /dev/uioX. Typically,
there'll be a loop or a thread with this blocking read() at the beginning
(usually using the select() call).
5.) At some time, a hardware interrupt will occur. The irq handler in kernel
space will be called, only to disable the irq. This will also cause the UIO
core to make /dev/uioX readable.
6.) Userspace's blocking read returns. Userspace does its work by
reading/writing device memory.
7.) As the last thing, Userspace writes a "1" to /dev/uioX, which causes
uio_pdrv_genirq_irqcontrol to be called, re-enabling the interrupt.
8.) Goto 4.)
We should also remember that uio_pdrv_genirq_handler() is NOT a real hardware
irq handler. The real handler is in the UIO core, which will increment the
event number and wake up userspace.
So, although your scenario clearly shows a subtle race condition, there is
none. If userspace does stupid things, no harm will be done to the kernel.
If userspace is designed the way described above (and in the documentation),
it will always wake up with its interrupt disabled, do its work, and then
re-enable the interrupt. You can probably think of a few things userspace
could do to screw things up. But that's not our problem.
Could you hack up a patch for that? I think it should start with removing
uio_pdrv_genirq_platdata->lock and uio_pdrv_genirq_platdata->flags...
Thanks again for your work. What do you think about my theory?
Thanks,
Hans
next prev parent reply other threads:[~2012-12-07 23:47 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-11-27 17:29 [PATCH] drivers/uio/uio_pdrv_genirq.c: Fix memory leak & confusing labels Vitalii Demianets
2012-11-27 23:07 ` Hans J. Koch
2012-11-28 0:07 ` Cong Ding
2012-11-28 0:37 ` Hans J. Koch
2012-11-28 9:29 ` Cong Ding
2012-11-28 21:09 ` Hans J. Koch
2012-11-28 9:37 ` Vitalii Demianets
2012-11-28 21:14 ` Hans J. Koch
2012-11-29 11:47 ` [PATCH] drivers/uio/uio_pdrv_genirq.c: Fix memory freeing issues Vitalii Demianets
2012-12-04 21:04 ` Hans J. Koch
2012-12-05 9:22 ` [PATCH v2] " Vitalii Demianets
2012-12-06 2:41 ` Hans J. Koch
2012-12-06 9:11 ` Vitalii Demianets
2012-12-06 22:15 ` Hans J. Koch
2012-12-07 9:41 ` Vitalii Demianets
2012-12-07 13:51 ` Vitalii Demianets
2012-12-07 15:00 ` Vitalii Demianets
2012-12-07 23:47 ` Hans J. Koch [this message]
2012-12-10 9:03 ` Vitalii Demianets
2012-12-10 9:52 ` Hans J. Koch
2012-12-10 10:24 ` Vitalii Demianets
2012-12-10 22:37 ` Hans J. Koch
2012-12-11 10:47 ` Vitalii Demianets
2012-12-13 17:11 ` Hans J. Koch
2012-12-13 17:23 ` Vitalii Demianets
2012-12-13 17:34 ` Hans J. Koch
2012-12-13 18:00 ` Vitalii Demianets
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20121207234720.GB3786@local \
--to=hjk@hansjkoch.de \
--cc=dinggnu@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=vitas@nppfactor.kiev.ua \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).