summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIan Abbott <abbotti@mev.co.uk>2014-10-28 13:07:22 +0000
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-10-29 16:15:07 +0800
commit73e0e4dfed4c5c3bd1533321c98f2dac379324f8 (patch)
treeef07127df4b0277d5e76756353b40c6739d09feb
parentbf33eb4b4f57d277eac611b5d331976059bcc700 (diff)
downloadlinux-73e0e4dfed4c5c3bd1533321c98f2dac379324f8.tar.bz2
staging: comedi: comedi_test: fix timer lock-up
Commit 240512474424 ("staging: comedi: comedi_test: use comedi_handle_events()") resulted in the timer routine `waveform_ai_interrupt()` calling `comedi_handle_events()` instead of `comedi_events()`. That had the advantage of automatically stopping the acquisition on overflow/error/end-of-acquisition conditions (by calling the comedi subdevice's "cancel" handler), but currently results in the timer routine locking when one of those conditions occur. This is because the "cancel" handler `waveform_ai_cancel()` calls `del_timer_sync()`. Fix it by adding a bit to the device private data that indicates whether the acquisition is active or not, and changing the "cancel" handler to use `del_timer()` instead of `del_timer_sync()`. The bit is set when starting the acquisition, cleared when ending the acquisition (in the "cancel" handler), and tested in the timer routine, which will do nothing if the acquisition is inactive. Also, make sure any scheduled timeout event gets cancelled when the low-level device gets "detached" from the comedi core by calling `del_timer_sync()` in the "detach" handler `waveform_detach()`. Fixes: 240512474424 ("staging: comedi: comedi_test: use comedi_handle_events()") Signed-off-by: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/staging/comedi/drivers/comedi_test.c21
1 files changed, 19 insertions, 2 deletions
diff --git a/drivers/staging/comedi/drivers/comedi_test.c b/drivers/staging/comedi/drivers/comedi_test.c
index b8fd4d24b421..8c348bbf9e71 100644
--- a/drivers/staging/comedi/drivers/comedi_test.c
+++ b/drivers/staging/comedi/drivers/comedi_test.c
@@ -56,6 +56,10 @@ zero volts).
#define N_CHANS 8
+enum waveform_state_bits {
+ WAVEFORM_AI_RUNNING = 0
+};
+
/* Data unique to this driver */
struct waveform_private {
struct timer_list timer;
@@ -65,6 +69,7 @@ struct waveform_private {
unsigned long usec_current; /* current time (mod waveform period) */
unsigned long usec_remainder; /* usec since last scan */
unsigned long ai_count; /* number of conversions remaining */
+ unsigned long state_bits;
unsigned int scan_period; /* scan period in usec */
unsigned int convert_period; /* conversion period in usec */
unsigned int ao_loopbacks[N_CHANS];
@@ -175,6 +180,10 @@ static void waveform_ai_interrupt(unsigned long arg)
ktime_t now;
bool stopping = false;
+ /* check command is still active */
+ if (!test_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits))
+ return;
+
now = ktime_get();
elapsed_time = ktime_to_us(ktime_sub(now, devpriv->last));
@@ -322,6 +331,10 @@ static int waveform_ai_cmd(struct comedi_device *dev,
devpriv->usec_remainder = 0;
devpriv->timer.expires = jiffies + 1;
+ /* mark command as active */
+ smp_mb__before_atomic();
+ set_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
+ smp_mb__after_atomic();
add_timer(&devpriv->timer);
return 0;
}
@@ -331,7 +344,11 @@ static int waveform_ai_cancel(struct comedi_device *dev,
{
struct waveform_private *devpriv = dev->private;
- del_timer_sync(&devpriv->timer);
+ /* mark command as no longer active */
+ clear_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
+ smp_mb__after_atomic();
+ /* cannot call del_timer_sync() as may be called from timer routine */
+ del_timer(&devpriv->timer);
return 0;
}
@@ -433,7 +450,7 @@ static void waveform_detach(struct comedi_device *dev)
struct waveform_private *devpriv = dev->private;
if (devpriv)
- waveform_ai_cancel(dev, dev->read_subdev);
+ del_timer_sync(&devpriv->timer);
}
static struct comedi_driver waveform_driver = {