2012-05-16 07:37:06

by Jiri Kosina

[permalink] [raw]
Subject: [PATCH] floppy: convert to delayed work and single-thread wq

There are several races in floppy driver between bottom half
(scheduled_work) and timers (fd_timeout, fd_timer). Due to slowness of the
actual floppy devices, those races are never (at least to my knowledge)
triggered on a bare floppy metal. However on virtualized (emulated) floppy
drives, which are of course magnitudes faster than the real ones, these
races trigger reliably. They usually exhibit themselves as NULL pointer
dereferences during DMA setup, such as

BUG: unable to handle kernel NULL pointer dereference at 0000000a
[ ... snip ... ]
EIP: 0060:[<c02053d5>] EFLAGS: 00010293 CPU: 0
EAX: ffffe000 EBX: 0000000a ECX: 00000000 EDX: 0000000a
ESI: c05d2718 EDI: 00000000 EBP: 00000000 ESP: f540fe44
DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068
Process swapper (pid: 0, ti=f540e000 task=c082d5a0 task.ti=c0826000)
Stack:
ffffe000 00001ffc 00000000 00000000 00000000 c05d2718 c0708b40 f540fe80
c020470f c05d2718 c0708b40 00000000 f540fe80 0000000a f540fee4 00000000
c0708b40 f540fee4 00000000 00000000 c020526b 00000000 c05d2718 c0708b40
Call Trace:
[<c020470f>] dump_trace+0xaf/0x110
[<c020526b>] show_trace_log_lvl+0x4b/0x60
[<c0205298>] show_trace+0x18/0x20
[<c05c5811>] dump_stack+0x6d/0x72
[<c0248527>] warn_slowpath_common+0x77/0xb0
[<c02485f3>] warn_slowpath_fmt+0x33/0x40
[<f7ec593c>] setup_DMA+0x14c/0x210 [floppy]
[<f7ecaa95>] setup_rw_floppy+0x105/0x190 [floppy]
[<c0256d08>] run_timer_softirq+0x168/0x2a0
[<c024e762>] __do_softirq+0xc2/0x1c0
[<c02042ed>] do_softirq+0x7d/0xb0
[<f54d8a00>] 0xf54d89ff

but other instances can be easily seen as well. This can be observed at
least under VMWare, VirtualBox and KVM.

This patch converts all the timers and bottom halfs to be processed in a
single workqueue. This aproach has been already discussed back in 2010 and
Acked by Linus [1], but it then never made it to the tree.

This all is based on original idea and code of Stephen Hemminger. I have
ported original Stepen's code to the current state of the floppy driver,
and performed quite some testing (on real hardware), which didn't reveal
any issues (this includes not only writing and reading data, but also
formatting (unfortunately I didn't find any Double-Density disks any
more)). Ability to handle errors properly (supplying known bad floppies)
has also been verified.

[1] http://kerneltrap.org/mailarchive/linux-kernel/2010/6/11/4582092

Based-on-patch-by: Stephen Hemminger <[email protected]>
Signed-off-by: Jiri Kosina <[email protected]>
---
drivers/block/floppy.c | 143 ++++++++++++++++++++++++------------------------
1 files changed, 72 insertions(+), 71 deletions(-)

diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c
index b0b00d7..9fffb03 100644
--- a/drivers/block/floppy.c
+++ b/drivers/block/floppy.c
@@ -551,7 +551,7 @@ static void floppy_ready(void);
static void floppy_start(void);
static void process_fd_request(void);
static void recalibrate_floppy(void);
-static void floppy_shutdown(unsigned long);
+static void floppy_shutdown(struct work_struct *);

static int floppy_request_regions(int);
static void floppy_release_regions(int);
@@ -588,6 +588,8 @@ static int buffer_max = -1;
static struct floppy_fdc_state fdc_state[N_FDC];
static int fdc; /* current fdc */

+static struct workqueue_struct *floppy_wq;
+
static struct floppy_struct *_floppy = floppy_type;
static unsigned char current_drive;
static long current_count_sectors;
@@ -629,16 +631,15 @@ static inline void set_debugt(void) { }
static inline void debugt(const char *func, const char *msg) { }
#endif /* DEBUGT */

-typedef void (*timeout_fn)(unsigned long);
-static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0);

+static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown);
static const char *timeout_message;

static void is_alive(const char *func, const char *message)
{
/* this routine checks whether the floppy driver is "alive" */
if (test_bit(0, &fdc_busy) && command_status < 2 &&
- !timer_pending(&fd_timeout)) {
+ !delayed_work_pending(&fd_timeout)) {
DPRINT("%s: timeout handler died. %s\n", func, message);
}
}
@@ -666,15 +667,18 @@ static int output_log_pos;

static void __reschedule_timeout(int drive, const char *message)
{
+ unsigned long delay;
+
if (drive == current_reqD)
drive = current_drive;
- del_timer(&fd_timeout);
+
if (drive < 0 || drive >= N_DRIVE) {
- fd_timeout.expires = jiffies + 20UL * HZ;
+ delay = 20UL * HZ;
drive = 0;
} else
- fd_timeout.expires = jiffies + UDP->timeout;
- add_timer(&fd_timeout);
+ delay = UDP->timeout;
+
+ queue_delayed_work(floppy_wq, &fd_timeout, delay);
if (UDP->flags & FD_DEBUG)
DPRINT("reschedule timeout %s\n", message);
timeout_message = message;
@@ -872,7 +876,7 @@ static int lock_fdc(int drive, bool interruptible)

command_status = FD_COMMAND_NONE;

- __reschedule_timeout(drive, "lock fdc");
+ reschedule_timeout(drive, "lock fdc");
set_fdc(drive);
return 0;
}
@@ -880,23 +884,15 @@ static int lock_fdc(int drive, bool interruptible)
/* unlocks the driver */
static void unlock_fdc(void)
{
- unsigned long flags;
-
- raw_cmd = NULL;
if (!test_bit(0, &fdc_busy))
DPRINT("FDC access conflict!\n");

- if (do_floppy)
- DPRINT("device interrupt still active at FDC release: %pf!\n",
- do_floppy);
+ raw_cmd = NULL;
command_status = FD_COMMAND_NONE;
- spin_lock_irqsave(&floppy_lock, flags);
- del_timer(&fd_timeout);
+ __cancel_delayed_work(&fd_timeout);
+ do_floppy = NULL;
cont = NULL;
clear_bit(0, &fdc_busy);
- if (current_req || set_next_request())
- do_fd_request(current_req->q);
- spin_unlock_irqrestore(&floppy_lock, flags);
wake_up(&fdc_wait);
}

@@ -969,25 +965,21 @@ static DECLARE_WORK(floppy_work, NULL);
static void schedule_bh(void (*handler)(void))
{
PREPARE_WORK(&floppy_work, (work_func_t)handler);
- schedule_work(&floppy_work);
+ queue_work(floppy_wq, &floppy_work);
}

-static DEFINE_TIMER(fd_timer, NULL, 0, 0);
+static DECLARE_DELAYED_WORK(fd_timer, NULL);

static void cancel_activity(void)
{
- unsigned long flags;
-
- spin_lock_irqsave(&floppy_lock, flags);
do_floppy = NULL;
- PREPARE_WORK(&floppy_work, (work_func_t)empty);
- del_timer(&fd_timer);
- spin_unlock_irqrestore(&floppy_lock, flags);
+ cancel_delayed_work_sync(&fd_timer);
+ cancel_work_sync(&floppy_work);
}

/* this function makes sure that the disk stays in the drive during the
* transfer */
-static void fd_watchdog(void)
+static void fd_watchdog(struct work_struct wq)
{
debug_dcl(DP->flags, "calling disk change from watchdog\n");

@@ -997,21 +989,20 @@ static void fd_watchdog(void)
cont->done(0);
reset_fdc();
} else {
- del_timer(&fd_timer);
- fd_timer.function = (timeout_fn)fd_watchdog;
- fd_timer.expires = jiffies + HZ / 10;
- add_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, fd_watchdog);
+ queue_delayed_work(floppy_wq, &fd_timer, HZ / 10);
}
}

static void main_command_interrupt(void)
{
- del_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
cont->interrupt();
}

/* waits for a delay (spinup or select) to pass */
-static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
+static int fd_wait_for_completion(unsigned long expires, work_func_t func)
{
if (FDCS->reset) {
reset_fdc(); /* do the reset during sleep to win time
@@ -1020,11 +1011,10 @@ static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
return 1;
}

- if (time_before(jiffies, delay)) {
- del_timer(&fd_timer);
- fd_timer.function = function;
- fd_timer.expires = delay;
- add_timer(&fd_timer);
+ if (time_before(jiffies, expires)) {
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, func);
+ queue_delayed_work(floppy_wq, &fd_timer, expires - jiffies);
return 1;
}
return 0;
@@ -1342,7 +1332,7 @@ static int fdc_dtr(void)
*/
FDCS->dtr = raw_cmd->rate & 3;
return fd_wait_for_completion(jiffies + 2UL * HZ / 100,
- (timeout_fn)floppy_ready);
+ (work_func_t)floppy_ready);
} /* fdc_dtr */

static void tell_sector(void)
@@ -1447,7 +1437,7 @@ static void setup_rw_floppy(void)
int flags;
int dflags;
unsigned long ready_date;
- timeout_fn function;
+ work_func_t func;

flags = raw_cmd->flags;
if (flags & (FD_RAW_READ | FD_RAW_WRITE))
@@ -1461,12 +1451,12 @@ static void setup_rw_floppy(void)
*/
if (time_after(ready_date, jiffies + DP->select_delay)) {
ready_date -= DP->select_delay;
- function = (timeout_fn)floppy_start;
+ func = (work_func_t)floppy_start;
} else
- function = (timeout_fn)setup_rw_floppy;
+ func = (work_func_t)setup_rw_floppy;

/* wait until the floppy is spinning fast enough */
- if (fd_wait_for_completion(ready_date, function))
+ if (fd_wait_for_completion(ready_date, func))
return;
}
dflags = DRS->flags;
@@ -1493,7 +1483,7 @@ static void setup_rw_floppy(void)
inr = result();
cont->interrupt();
} else if (flags & FD_RAW_NEED_DISK)
- fd_watchdog();
+ fd_watchdog(NULL);
}

static int blind_seek;
@@ -1802,20 +1792,22 @@ static void show_floppy(void)
pr_info("do_floppy=%pf\n", do_floppy);
if (work_pending(&floppy_work))
pr_info("floppy_work.func=%pf\n", floppy_work.func);
- if (timer_pending(&fd_timer))
- pr_info("fd_timer.function=%pf\n", fd_timer.function);
- if (timer_pending(&fd_timeout)) {
- pr_info("timer_function=%pf\n", fd_timeout.function);
- pr_info("expires=%lu\n", fd_timeout.expires - jiffies);
- pr_info("now=%lu\n", jiffies);
- }
+ if (delayed_work_pending(&fd_timeout))
+ pr_info("timer_function=%p expires=%ld\n",
+ fd_timeout.work.func,
+ fd_timeout.timer.expires - jiffies);
+ if (delayed_work_pending(&fd_timer))
+ pr_info("delayed work.function=%p expires=%ld\n",
+ fd_timer.work.func,
+ fd_timer.timer.expires - jiffies);
+
pr_info("cont=%p\n", cont);
pr_info("current_req=%p\n", current_req);
pr_info("command_status=%d\n", command_status);
pr_info("\n");
}

-static void floppy_shutdown(unsigned long data)
+static void floppy_shutdown(struct work_struct *wq)
{
unsigned long flags;

@@ -1868,7 +1860,7 @@ static int start_motor(void (*function)(void))

/* wait_for_completion also schedules reset if needed. */
return fd_wait_for_completion(DRS->select_date + DP->select_delay,
- (timeout_fn)function);
+ (work_func_t)function);
}

static void floppy_ready(void)
@@ -2821,7 +2813,6 @@ do_request:
spin_lock_irq(&floppy_lock);
pending = set_next_request();
spin_unlock_irq(&floppy_lock);
-
if (!pending) {
do_floppy = NULL;
unlock_fdc();
@@ -2898,13 +2889,15 @@ static void do_fd_request(struct request_queue *q)
current_req->cmd_flags))
return;

- if (test_bit(0, &fdc_busy)) {
+ if (test_and_set_bit(0, &fdc_busy)) {
/* fdc busy, this new request will be treated when the
current one is done */
is_alive(__func__, "old request running");
return;
}
- lock_fdc(MAXTIMEOUT, false);
+ command_status = FD_COMMAND_NONE;
+ __reschedule_timeout(MAXTIMEOUT, "fd_request");
+ set_fdc(0);
process_fd_request();
is_alive(__func__, "");
}
@@ -4159,10 +4152,16 @@ static int __init floppy_init(void)
goto out_put_disk;
}

+ floppy_wq = create_singlethread_workqueue("floppy");
+ if (!floppy_wq) {
+ err = -ENOMEM;
+ goto out_put_disk;
+ }
+
disks[dr]->queue = blk_init_queue(do_fd_request, &floppy_lock);
if (!disks[dr]->queue) {
err = -ENOMEM;
- goto out_put_disk;
+ goto out_destroy_workq;
}

blk_queue_max_hw_sectors(disks[dr]->queue, 64);
@@ -4213,7 +4212,7 @@ static int __init floppy_init(void)
use_virtual_dma = can_use_virtual_dma & 1;
fdc_state[0].address = FDC1;
if (fdc_state[0].address == -1) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -ENODEV;
goto out_unreg_region;
}
@@ -4224,7 +4223,7 @@ static int __init floppy_init(void)
fdc = 0; /* reset fdc in case of unexpected interrupt */
err = floppy_grab_irq_and_dma();
if (err) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -EBUSY;
goto out_unreg_region;
}
@@ -4281,13 +4280,13 @@ static int __init floppy_init(void)
user_reset_fdc(-1, FD_RESET_ALWAYS, false);
}
fdc = 0;
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
current_drive = 0;
initialized = true;
if (have_no_fdc) {
DPRINT("no floppy controllers found\n");
err = have_no_fdc;
- goto out_flush_work;
+ goto out_release_dma;
}

for (drive = 0; drive < N_DRIVE; drive++) {
@@ -4302,7 +4301,7 @@ static int __init floppy_init(void)

err = platform_device_register(&floppy_device[drive]);
if (err)
- goto out_flush_work;
+ goto out_release_dma;

err = device_create_file(&floppy_device[drive].dev,
&dev_attr_cmos);
@@ -4320,13 +4319,14 @@ static int __init floppy_init(void)

out_unreg_platform_dev:
platform_device_unregister(&floppy_device[drive]);
-out_flush_work:
- flush_work_sync(&floppy_work);
+out_release_dma:
if (atomic_read(&usage_count))
floppy_release_irq_and_dma();
out_unreg_region:
blk_unregister_region(MKDEV(FLOPPY_MAJOR, 0), 256);
platform_driver_unregister(&floppy_driver);
+out_destroy_workq:
+ destroy_workqueue(floppy_wq);
out_unreg_blkdev:
unregister_blkdev(FLOPPY_MAJOR, "fd");
out_put_disk:
@@ -4397,7 +4397,7 @@ static int floppy_grab_irq_and_dma(void)
* We might have scheduled a free_irq(), wait it to
* drain first:
*/
- flush_work_sync(&floppy_work);
+ flush_workqueue(floppy_wq);

if (fd_request_irq()) {
DPRINT("Unable to grab IRQ%d for the floppy driver\n",
@@ -4488,9 +4488,9 @@ static void floppy_release_irq_and_dma(void)
pr_info("motor off timer %d still active\n", drive);
#endif

- if (timer_pending(&fd_timeout))
+ if (delayed_work_pending(&fd_timeout))
pr_info("floppy timer still active:%s\n", timeout_message);
- if (timer_pending(&fd_timer))
+ if (delayed_work_pending(&fd_timer))
pr_info("auxiliary floppy timer still active\n");
if (work_pending(&floppy_work))
pr_info("work still pending\n");
@@ -4560,8 +4560,9 @@ static void __exit floppy_module_exit(void)
put_disk(disks[drive]);
}

- del_timer_sync(&fd_timeout);
- del_timer_sync(&fd_timer);
+ cancel_delayed_work_sync(&fd_timeout);
+ cancel_delayed_work_sync(&fd_timer);
+ destroy_workqueue(floppy_wq);

if (atomic_read(&usage_count))
floppy_release_irq_and_dma();

--
Jiri Kosina
SUSE Labs


2012-05-16 14:57:25

by Stephen Hemminger

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, 16 May 2012 09:36:51 +0200 (CEST)
Jiri Kosina <[email protected]> wrote:

> There are several races in floppy driver between bottom half
> (scheduled_work) and timers (fd_timeout, fd_timer). Due to slowness of the
> actual floppy devices, those races are never (at least to my knowledge)
> triggered on a bare floppy metal. However on virtualized (emulated) floppy
> drives, which are of course magnitudes faster than the real ones, these
> races trigger reliably. They usually exhibit themselves as NULL pointer
> dereferences during DMA setup, such as
>
> BUG: unable to handle kernel NULL pointer dereference at 0000000a
> [ ... snip ... ]
> EIP: 0060:[<c02053d5>] EFLAGS: 00010293 CPU: 0
> EAX: ffffe000 EBX: 0000000a ECX: 00000000 EDX: 0000000a
> ESI: c05d2718 EDI: 00000000 EBP: 00000000 ESP: f540fe44
> DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068
> Process swapper (pid: 0, ti=f540e000 task=c082d5a0 task.ti=c0826000)
> Stack:
> ffffe000 00001ffc 00000000 00000000 00000000 c05d2718 c0708b40 f540fe80
> c020470f c05d2718 c0708b40 00000000 f540fe80 0000000a f540fee4 00000000
> c0708b40 f540fee4 00000000 00000000 c020526b 00000000 c05d2718 c0708b40
> Call Trace:
> [<c020470f>] dump_trace+0xaf/0x110
> [<c020526b>] show_trace_log_lvl+0x4b/0x60
> [<c0205298>] show_trace+0x18/0x20
> [<c05c5811>] dump_stack+0x6d/0x72
> [<c0248527>] warn_slowpath_common+0x77/0xb0
> [<c02485f3>] warn_slowpath_fmt+0x33/0x40
> [<f7ec593c>] setup_DMA+0x14c/0x210 [floppy]
> [<f7ecaa95>] setup_rw_floppy+0x105/0x190 [floppy]
> [<c0256d08>] run_timer_softirq+0x168/0x2a0
> [<c024e762>] __do_softirq+0xc2/0x1c0
> [<c02042ed>] do_softirq+0x7d/0xb0
> [<f54d8a00>] 0xf54d89ff
>
> but other instances can be easily seen as well. This can be observed at
> least under VMWare, VirtualBox and KVM.
>
> This patch converts all the timers and bottom halfs to be processed in a
> single workqueue. This aproach has been already discussed back in 2010 and
> Acked by Linus [1], but it then never made it to the tree.
>
> This all is based on original idea and code of Stephen Hemminger. I have
> ported original Stepen's code to the current state of the floppy driver,
> and performed quite some testing (on real hardware), which didn't reveal
> any issues (this includes not only writing and reading data, but also
> formatting (unfortunately I didn't find any Double-Density disks any
> more)). Ability to handle errors properly (supplying known bad floppies)
> has also been verified.
>
> [1] http://kerneltrap.org/mailarchive/linux-kernel/2010/6/11/4582092
>
> Based-on-patch-by: Stephen Hemminger <[email protected]>
> Signed-off-by: Jiri Kosina <[email protected]>

Thanks, the hold up for me was always finding real working floppy
drive to test.

2012-05-16 15:25:30

by Linus Torvalds

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 12:36 AM, Jiri Kosina <[email protected]> wrote:
>
> This patch converts all the timers and bottom halfs to be processed in a
> single workqueue. This aproach has been already discussed back in 2010 and
> Acked by Linus [1], but it then never made it to the tree.

Nobody ever actually reported testing it on real hardware, and I
didn't want to take it untested.

Since you have tested this, I will have *zero* problems with taking it for 3.5.

Linus

2012-05-16 17:01:34

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 09:36:51AM +0200, Jiri Kosina wrote:
> +static struct workqueue_struct *floppy_wq;
> +
> static struct floppy_struct *_floppy = floppy_type;
> static unsigned char current_drive;
> static long current_count_sectors;
> @@ -629,16 +631,15 @@ static inline void set_debugt(void) { }
> static inline void debugt(const char *func, const char *msg) { }
> #endif /* DEBUGT */
>
> -typedef void (*timeout_fn)(unsigned long);
> -static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0);
>
> +static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown);
> static const char *timeout_message;

There's no need to create a separate workqueue for this. There's just
single work item which shouldn't be executed concurrently on different
CPUs, right? Just queueing the work item onto system_nrt_wq should be
enough.

Thanks.

--
tejun

2012-05-16 19:37:55

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, 16 May 2012, Stephen Hemminger wrote:

> Thanks, the hold up for me was always finding real working floppy
> drive to test.

On Wed, 16 May 2012, Linus Torvalds wrote:

> > This patch converts all the timers and bottom halfs to be processed in a
> > single workqueue. This aproach has been already discussed back in 2010 and
> > Acked by Linus [1], but it then never made it to the tree.
>
> Nobody ever actually reported testing it on real hardware, and I
> didn't want to take it untested.
>
> Since you have tested this, I will have *zero* problems with taking it
> for 3.5.

Thanks. From your responses I gather that there is actually a slight
problem with not enough real floppy drives being owned by kernel people.

As I currently still own quite a few machines with those drives, I can
eventually take over some kind of floppy.c maintainership if necessary.

On Wed, 16 May 2012, Tejun Heo wrote:

> > +static struct workqueue_struct *floppy_wq;
> > +
> > static struct floppy_struct *_floppy = floppy_type;
> > static unsigned char current_drive;
> > static long current_count_sectors;
> > @@ -629,16 +631,15 @@ static inline void set_debugt(void) { }
> > static inline void debugt(const char *func, const char *msg) { }
> > #endif /* DEBUGT */
> >
> > -typedef void (*timeout_fn)(unsigned long);
> > -static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0);
> >
> > +static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown);
> > static const char *timeout_message;
>
> There's no need to create a separate workqueue for this. There's just
> single work item which shouldn't be executed concurrently on different
> CPUs, right? Just queueing the work item onto system_nrt_wq should be
> enough.

You are right. Updated patch below. I have again perfomed the same set of
tests as with the previous version.

Who is going to take this please? (or should I already? :) )




From: Jiri Kosina <[email protected]>
Subject: [PATCH] floppy: convert to delayed work and single-thread wq

There are several races in floppy driver between bottom half
(scheduled_work) and timers (fd_timeout, fd_timer). Due to slowness
of the actual floppy devices, those races are never (at least to my
knowledge) triggered on a bare floppy metal. However on virtualized
(emulated) floppy drives, which are of course magnitudes faster
than the real ones, these races trigger reliably. They usually exhibit
themselves as NULL pointer dereferences during DMA setup, such as

BUG: unable to handle kernel NULL pointer dereference at 0000000a
[ ... snip ... ]
EIP: 0060:[<c02053d5>] EFLAGS: 00010293 CPU: 0
EAX: ffffe000 EBX: 0000000a ECX: 00000000 EDX: 0000000a
ESI: c05d2718 EDI: 00000000 EBP: 00000000 ESP: f540fe44
DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068
Process swapper (pid: 0, ti=f540e000 task=c082d5a0 task.ti=c0826000)
Stack:
ffffe000 00001ffc 00000000 00000000 00000000 c05d2718 c0708b40 f540fe80
c020470f c05d2718 c0708b40 00000000 f540fe80 0000000a f540fee4 00000000
c0708b40 f540fee4 00000000 00000000 c020526b 00000000 c05d2718 c0708b40
Call Trace:
[<c020470f>] dump_trace+0xaf/0x110
[<c020526b>] show_trace_log_lvl+0x4b/0x60
[<c0205298>] show_trace+0x18/0x20
[<c05c5811>] dump_stack+0x6d/0x72
[<c0248527>] warn_slowpath_common+0x77/0xb0
[<c02485f3>] warn_slowpath_fmt+0x33/0x40
[<f7ec593c>] setup_DMA+0x14c/0x210 [floppy]
[<f7ecaa95>] setup_rw_floppy+0x105/0x190 [floppy]
[<c0256d08>] run_timer_softirq+0x168/0x2a0
[<c024e762>] __do_softirq+0xc2/0x1c0
[<c02042ed>] do_softirq+0x7d/0xb0
[<f54d8a00>] 0xf54d89ff

but other instances can be easily seen as well. This can be observed at least under
VMWare, VirtualBox and KVM.

This patch converts all the timers and bottom halfs to be processed in a
single, system-wide, single-threaded workqueue (system_nrt_wq). This
aproach has been already discussed back in 2010 if I remember correctly,
and Acked by Linus [1], but it then never made it to the tree.

This all is based on original idea and code of Stephen Hemminger. I have
ported original Stepen's code to the current state of the floppy driver, and
performed quite some testing (on real hardware), which didn't reveal any issues
(this includes not only writing and reading data, but also formatting
(unfortunately I didn't find any Double-Density disks any more)). Ability to
handle errors properly (supplying known bad floppies) has also been verified.

[1] http://kerneltrap.org/mailarchive/linux-kernel/2010/6/11/4582092

Based-on-patch-by: Stephen Hemminger <[email protected]>
Signed-off-by: Jiri Kosina <[email protected]>
---
drivers/block/floppy.c | 132 +++++++++++++++++++++++-------------------------
1 files changed, 63 insertions(+), 69 deletions(-)

diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c
index b0b00d7..d4eb8af 100644
--- a/drivers/block/floppy.c
+++ b/drivers/block/floppy.c
@@ -551,7 +551,7 @@ static void floppy_ready(void);
static void floppy_start(void);
static void process_fd_request(void);
static void recalibrate_floppy(void);
-static void floppy_shutdown(unsigned long);
+static void floppy_shutdown(struct work_struct *);

static int floppy_request_regions(int);
static void floppy_release_regions(int);
@@ -629,16 +629,15 @@ static inline void set_debugt(void) { }
static inline void debugt(const char *func, const char *msg) { }
#endif /* DEBUGT */

-typedef void (*timeout_fn)(unsigned long);
-static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0);

+static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown);
static const char *timeout_message;

static void is_alive(const char *func, const char *message)
{
/* this routine checks whether the floppy driver is "alive" */
if (test_bit(0, &fdc_busy) && command_status < 2 &&
- !timer_pending(&fd_timeout)) {
+ !delayed_work_pending(&fd_timeout)) {
DPRINT("%s: timeout handler died. %s\n", func, message);
}
}
@@ -666,15 +665,18 @@ static int output_log_pos;

static void __reschedule_timeout(int drive, const char *message)
{
+ unsigned long delay;
+
if (drive == current_reqD)
drive = current_drive;
- del_timer(&fd_timeout);
+
if (drive < 0 || drive >= N_DRIVE) {
- fd_timeout.expires = jiffies + 20UL * HZ;
+ delay = 20UL * HZ;
drive = 0;
} else
- fd_timeout.expires = jiffies + UDP->timeout;
- add_timer(&fd_timeout);
+ delay = UDP->timeout;
+
+ queue_delayed_work(system_nrt_wq, &fd_timeout, delay);
if (UDP->flags & FD_DEBUG)
DPRINT("reschedule timeout %s\n", message);
timeout_message = message;
@@ -872,7 +874,7 @@ static int lock_fdc(int drive, bool interruptible)

command_status = FD_COMMAND_NONE;

- __reschedule_timeout(drive, "lock fdc");
+ reschedule_timeout(drive, "lock fdc");
set_fdc(drive);
return 0;
}
@@ -880,23 +882,15 @@ static int lock_fdc(int drive, bool interruptible)
/* unlocks the driver */
static void unlock_fdc(void)
{
- unsigned long flags;
-
- raw_cmd = NULL;
if (!test_bit(0, &fdc_busy))
DPRINT("FDC access conflict!\n");

- if (do_floppy)
- DPRINT("device interrupt still active at FDC release: %pf!\n",
- do_floppy);
+ raw_cmd = NULL;
command_status = FD_COMMAND_NONE;
- spin_lock_irqsave(&floppy_lock, flags);
- del_timer(&fd_timeout);
+ __cancel_delayed_work(&fd_timeout);
+ do_floppy = NULL;
cont = NULL;
clear_bit(0, &fdc_busy);
- if (current_req || set_next_request())
- do_fd_request(current_req->q);
- spin_unlock_irqrestore(&floppy_lock, flags);
wake_up(&fdc_wait);
}

@@ -968,26 +962,24 @@ static DECLARE_WORK(floppy_work, NULL);

static void schedule_bh(void (*handler)(void))
{
+ WARN_ON(work_pending(&floppy_work));
+
PREPARE_WORK(&floppy_work, (work_func_t)handler);
- schedule_work(&floppy_work);
+ queue_work(system_nrt_wq, &floppy_work);
}

-static DEFINE_TIMER(fd_timer, NULL, 0, 0);
+static DECLARE_DELAYED_WORK(fd_timer, NULL);

static void cancel_activity(void)
{
- unsigned long flags;
-
- spin_lock_irqsave(&floppy_lock, flags);
do_floppy = NULL;
- PREPARE_WORK(&floppy_work, (work_func_t)empty);
- del_timer(&fd_timer);
- spin_unlock_irqrestore(&floppy_lock, flags);
+ cancel_delayed_work_sync(&fd_timer);
+ cancel_work_sync(&floppy_work);
}

/* this function makes sure that the disk stays in the drive during the
* transfer */
-static void fd_watchdog(void)
+static void fd_watchdog(struct work_struct *arg)
{
debug_dcl(DP->flags, "calling disk change from watchdog\n");

@@ -997,21 +989,21 @@ static void fd_watchdog(void)
cont->done(0);
reset_fdc();
} else {
- del_timer(&fd_timer);
- fd_timer.function = (timeout_fn)fd_watchdog;
- fd_timer.expires = jiffies + HZ / 10;
- add_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, fd_watchdog);
+ queue_delayed_work(system_nrt_wq,
+ &fd_timer, HZ / 10);
}
}

static void main_command_interrupt(void)
{
- del_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
cont->interrupt();
}

/* waits for a delay (spinup or select) to pass */
-static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
+static int fd_wait_for_completion(unsigned long expires, work_func_t function)
{
if (FDCS->reset) {
reset_fdc(); /* do the reset during sleep to win time
@@ -1020,11 +1012,10 @@ static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
return 1;
}

- if (time_before(jiffies, delay)) {
- del_timer(&fd_timer);
- fd_timer.function = function;
- fd_timer.expires = delay;
- add_timer(&fd_timer);
+ if (time_before(jiffies, expires)) {
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, function);
+ queue_delayed_work(system_nrt_wq, &fd_timer, expires - jiffies);
return 1;
}
return 0;
@@ -1342,7 +1333,7 @@ static int fdc_dtr(void)
*/
FDCS->dtr = raw_cmd->rate & 3;
return fd_wait_for_completion(jiffies + 2UL * HZ / 100,
- (timeout_fn)floppy_ready);
+ (work_func_t)floppy_ready);
} /* fdc_dtr */

static void tell_sector(void)
@@ -1447,7 +1438,7 @@ static void setup_rw_floppy(void)
int flags;
int dflags;
unsigned long ready_date;
- timeout_fn function;
+ work_func_t function;

flags = raw_cmd->flags;
if (flags & (FD_RAW_READ | FD_RAW_WRITE))
@@ -1461,9 +1452,9 @@ static void setup_rw_floppy(void)
*/
if (time_after(ready_date, jiffies + DP->select_delay)) {
ready_date -= DP->select_delay;
- function = (timeout_fn)floppy_start;
+ function = (work_func_t)floppy_start;
} else
- function = (timeout_fn)setup_rw_floppy;
+ function = (work_func_t)setup_rw_floppy;

/* wait until the floppy is spinning fast enough */
if (fd_wait_for_completion(ready_date, function))
@@ -1493,7 +1484,7 @@ static void setup_rw_floppy(void)
inr = result();
cont->interrupt();
} else if (flags & FD_RAW_NEED_DISK)
- fd_watchdog();
+ fd_watchdog(NULL);
}

static int blind_seek;
@@ -1802,20 +1793,22 @@ static void show_floppy(void)
pr_info("do_floppy=%pf\n", do_floppy);
if (work_pending(&floppy_work))
pr_info("floppy_work.func=%pf\n", floppy_work.func);
- if (timer_pending(&fd_timer))
- pr_info("fd_timer.function=%pf\n", fd_timer.function);
- if (timer_pending(&fd_timeout)) {
- pr_info("timer_function=%pf\n", fd_timeout.function);
- pr_info("expires=%lu\n", fd_timeout.expires - jiffies);
- pr_info("now=%lu\n", jiffies);
- }
+ if (delayed_work_pending(&fd_timer))
+ pr_info("delayed work.function=%p expires=%ld\n",
+ fd_timer.work.func,
+ fd_timer.timer.expires - jiffies);
+ if (delayed_work_pending(&fd_timeout))
+ pr_info("timer_function=%p expires=%ld\n",
+ fd_timeout.work.func,
+ fd_timeout.timer.expires - jiffies);
+
pr_info("cont=%p\n", cont);
pr_info("current_req=%p\n", current_req);
pr_info("command_status=%d\n", command_status);
pr_info("\n");
}

-static void floppy_shutdown(unsigned long data)
+static void floppy_shutdown(struct work_struct *arg)
{
unsigned long flags;

@@ -1868,7 +1861,7 @@ static int start_motor(void (*function)(void))

/* wait_for_completion also schedules reset if needed. */
return fd_wait_for_completion(DRS->select_date + DP->select_delay,
- (timeout_fn)function);
+ (work_func_t)function);
}

static void floppy_ready(void)
@@ -2821,7 +2814,6 @@ do_request:
spin_lock_irq(&floppy_lock);
pending = set_next_request();
spin_unlock_irq(&floppy_lock);
-
if (!pending) {
do_floppy = NULL;
unlock_fdc();
@@ -2898,13 +2890,15 @@ static void do_fd_request(struct request_queue *q)
current_req->cmd_flags))
return;

- if (test_bit(0, &fdc_busy)) {
+ if (test_and_set_bit(0, &fdc_busy)) {
/* fdc busy, this new request will be treated when the
current one is done */
is_alive(__func__, "old request running");
return;
}
- lock_fdc(MAXTIMEOUT, false);
+ command_status = FD_COMMAND_NONE;
+ __reschedule_timeout(MAXTIMEOUT, "fd_request");
+ set_fdc(0);
process_fd_request();
is_alive(__func__, "");
}
@@ -4213,7 +4207,7 @@ static int __init floppy_init(void)
use_virtual_dma = can_use_virtual_dma & 1;
fdc_state[0].address = FDC1;
if (fdc_state[0].address == -1) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -ENODEV;
goto out_unreg_region;
}
@@ -4224,7 +4218,7 @@ static int __init floppy_init(void)
fdc = 0; /* reset fdc in case of unexpected interrupt */
err = floppy_grab_irq_and_dma();
if (err) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -EBUSY;
goto out_unreg_region;
}
@@ -4281,13 +4275,13 @@ static int __init floppy_init(void)
user_reset_fdc(-1, FD_RESET_ALWAYS, false);
}
fdc = 0;
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
current_drive = 0;
initialized = true;
if (have_no_fdc) {
DPRINT("no floppy controllers found\n");
err = have_no_fdc;
- goto out_flush_work;
+ goto out_release_dma;
}

for (drive = 0; drive < N_DRIVE; drive++) {
@@ -4302,7 +4296,7 @@ static int __init floppy_init(void)

err = platform_device_register(&floppy_device[drive]);
if (err)
- goto out_flush_work;
+ goto out_release_dma;

err = device_create_file(&floppy_device[drive].dev,
&dev_attr_cmos);
@@ -4320,8 +4314,7 @@ static int __init floppy_init(void)

out_unreg_platform_dev:
platform_device_unregister(&floppy_device[drive]);
-out_flush_work:
- flush_work_sync(&floppy_work);
+out_release_dma:
if (atomic_read(&usage_count))
floppy_release_irq_and_dma();
out_unreg_region:
@@ -4397,7 +4390,7 @@ static int floppy_grab_irq_and_dma(void)
* We might have scheduled a free_irq(), wait it to
* drain first:
*/
- flush_work_sync(&floppy_work);
+ flush_workqueue(system_nrt_wq);

if (fd_request_irq()) {
DPRINT("Unable to grab IRQ%d for the floppy driver\n",
@@ -4488,9 +4481,9 @@ static void floppy_release_irq_and_dma(void)
pr_info("motor off timer %d still active\n", drive);
#endif

- if (timer_pending(&fd_timeout))
+ if (delayed_work_pending(&fd_timeout))
pr_info("floppy timer still active:%s\n", timeout_message);
- if (timer_pending(&fd_timer))
+ if (delayed_work_pending(&fd_timer))
pr_info("auxiliary floppy timer still active\n");
if (work_pending(&floppy_work))
pr_info("work still pending\n");
@@ -4560,8 +4553,9 @@ static void __exit floppy_module_exit(void)
put_disk(disks[drive]);
}

- del_timer_sync(&fd_timeout);
- del_timer_sync(&fd_timer);
+ cancel_delayed_work_sync(&fd_timeout);
+ cancel_delayed_work_sync(&fd_timer);
+ destroy_workqueue(system_nrt_wq);

if (atomic_read(&usage_count))
floppy_release_irq_and_dma();

--
Jiri Kosina
SUSE Labs

2012-05-16 19:44:21

by Linus Torvalds

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 12:37 PM, Jiri Kosina <[email protected]> wrote:
>
> Thanks. From your responses I gather that there is actually a slight
> problem with not enough real floppy drives being owned by kernel people.

Absolutely. We've had bug-reports about floppies not working, with no
real way to do anything about it.

In fact, I would even mark the patch for [email protected],
although I'd like it to be in 3.5 for a while before actually
backported. Exactly because we *have* had reports of problems, even
though I think they've mostly been in VM's.

> As I currently still own quite a few machines with those drives, I can
> eventually take over some kind of floppy.c maintainership if necessary.

You'll have to fight the current maintainer to the death in the Thunderdome.

What's that, you say? Oh, that there is no current maintainer? Oh,
well. I guess you won't have to fight anybody then.

Tag, you're it.

Linus

2012-05-16 19:47:52

by Linus Torvalds

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 12:37 PM, Jiri Kosina <[email protected]> wrote:
> + ? ? ? cancel_delayed_work_sync(&fd_timeout);
> + ? ? ? cancel_delayed_work_sync(&fd_timer);
> + ? ? ? destroy_workqueue(system_nrt_wq);

Well, *that* doesn't look right.

I think just a "flush_workqueue()" is in order.

Linus

2012-05-16 19:52:10

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, 16 May 2012, Linus Torvalds wrote:

> > + ? ? ? cancel_delayed_work_sync(&fd_timeout);
> > + ? ? ? cancel_delayed_work_sync(&fd_timer);
> > + ? ? ? destroy_workqueue(system_nrt_wq);
>
> Well, *that* doesn't look right.

Oh indeed, that was a little bit too straightforward conversion from the
original version :) Thanks for spotting that.

Updated patch below.



From: Jiri Kosina <[email protected]>
Subject: [PATCH] floppy: convert to delayed work and single-thread wq

There are several races in floppy driver between bottom half
(scheduled_work) and timers (fd_timeout, fd_timer). Due to slowness
of the actual floppy devices, those races are never (at least to my
knowledge) triggered on a bare floppy metal. However on virtualized
(emulated) floppy drives, which are of course magnitudes faster
than the real ones, these races trigger reliably. They usually exhibit
themselves as NULL pointer dereferences during DMA setup, such as

BUG: unable to handle kernel NULL pointer dereference at 0000000a
[ ... snip ... ]
EIP: 0060:[<c02053d5>] EFLAGS: 00010293 CPU: 0
EAX: ffffe000 EBX: 0000000a ECX: 00000000 EDX: 0000000a
ESI: c05d2718 EDI: 00000000 EBP: 00000000 ESP: f540fe44
DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068
Process swapper (pid: 0, ti=f540e000 task=c082d5a0 task.ti=c0826000)
Stack:
ffffe000 00001ffc 00000000 00000000 00000000 c05d2718 c0708b40 f540fe80
c020470f c05d2718 c0708b40 00000000 f540fe80 0000000a f540fee4 00000000
c0708b40 f540fee4 00000000 00000000 c020526b 00000000 c05d2718 c0708b40
Call Trace:
[<c020470f>] dump_trace+0xaf/0x110
[<c020526b>] show_trace_log_lvl+0x4b/0x60
[<c0205298>] show_trace+0x18/0x20
[<c05c5811>] dump_stack+0x6d/0x72
[<c0248527>] warn_slowpath_common+0x77/0xb0
[<c02485f3>] warn_slowpath_fmt+0x33/0x40
[<f7ec593c>] setup_DMA+0x14c/0x210 [floppy]
[<f7ecaa95>] setup_rw_floppy+0x105/0x190 [floppy]
[<c0256d08>] run_timer_softirq+0x168/0x2a0
[<c024e762>] __do_softirq+0xc2/0x1c0
[<c02042ed>] do_softirq+0x7d/0xb0
[<f54d8a00>] 0xf54d89ff

but other instances can be easily seen as well. This can be observed at least under
VMWare, VirtualBox and KVM.

This patch converts all the timers and bottom halfs to be processed in a single
workqueue. This aproach has been already discussed back in 2010 if I remember
correctly, and Acked by Linus [1], but it then never made it to the tree.

This all is based on original idea and code of Stephen Hemminger. I have
ported original Stepen's code to the current state of the floppy driver, and
performed quite some testing (on real hardware), which didn't reveal any issues
(this includes not only writing and reading data, but also formatting
(unfortunately I didn't find any Double-Density disks any more)). Ability to
handle errors properly (supplying known bad floppies) has also been verified.

[1] http://kerneltrap.org/mailarchive/linux-kernel/2010/6/11/4582092

Based-on-patch-by: Stephen Hemminger <[email protected]>
Signed-off-by: Jiri Kosina <[email protected]>
---
drivers/block/floppy.c | 132 +++++++++++++++++++++++-------------------------
1 files changed, 63 insertions(+), 69 deletions(-)

diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c
index b0b00d7..b58df4d 100644
--- a/drivers/block/floppy.c
+++ b/drivers/block/floppy.c
@@ -551,7 +551,7 @@ static void floppy_ready(void);
static void floppy_start(void);
static void process_fd_request(void);
static void recalibrate_floppy(void);
-static void floppy_shutdown(unsigned long);
+static void floppy_shutdown(struct work_struct *);

static int floppy_request_regions(int);
static void floppy_release_regions(int);
@@ -629,16 +629,15 @@ static inline void set_debugt(void) { }
static inline void debugt(const char *func, const char *msg) { }
#endif /* DEBUGT */

-typedef void (*timeout_fn)(unsigned long);
-static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0);

+static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown);
static const char *timeout_message;

static void is_alive(const char *func, const char *message)
{
/* this routine checks whether the floppy driver is "alive" */
if (test_bit(0, &fdc_busy) && command_status < 2 &&
- !timer_pending(&fd_timeout)) {
+ !delayed_work_pending(&fd_timeout)) {
DPRINT("%s: timeout handler died. %s\n", func, message);
}
}
@@ -666,15 +665,18 @@ static int output_log_pos;

static void __reschedule_timeout(int drive, const char *message)
{
+ unsigned long delay;
+
if (drive == current_reqD)
drive = current_drive;
- del_timer(&fd_timeout);
+
if (drive < 0 || drive >= N_DRIVE) {
- fd_timeout.expires = jiffies + 20UL * HZ;
+ delay = 20UL * HZ;
drive = 0;
} else
- fd_timeout.expires = jiffies + UDP->timeout;
- add_timer(&fd_timeout);
+ delay = UDP->timeout;
+
+ queue_delayed_work(system_nrt_wq, &fd_timeout, delay);
if (UDP->flags & FD_DEBUG)
DPRINT("reschedule timeout %s\n", message);
timeout_message = message;
@@ -872,7 +874,7 @@ static int lock_fdc(int drive, bool interruptible)

command_status = FD_COMMAND_NONE;

- __reschedule_timeout(drive, "lock fdc");
+ reschedule_timeout(drive, "lock fdc");
set_fdc(drive);
return 0;
}
@@ -880,23 +882,15 @@ static int lock_fdc(int drive, bool interruptible)
/* unlocks the driver */
static void unlock_fdc(void)
{
- unsigned long flags;
-
- raw_cmd = NULL;
if (!test_bit(0, &fdc_busy))
DPRINT("FDC access conflict!\n");

- if (do_floppy)
- DPRINT("device interrupt still active at FDC release: %pf!\n",
- do_floppy);
+ raw_cmd = NULL;
command_status = FD_COMMAND_NONE;
- spin_lock_irqsave(&floppy_lock, flags);
- del_timer(&fd_timeout);
+ __cancel_delayed_work(&fd_timeout);
+ do_floppy = NULL;
cont = NULL;
clear_bit(0, &fdc_busy);
- if (current_req || set_next_request())
- do_fd_request(current_req->q);
- spin_unlock_irqrestore(&floppy_lock, flags);
wake_up(&fdc_wait);
}

@@ -968,26 +962,24 @@ static DECLARE_WORK(floppy_work, NULL);

static void schedule_bh(void (*handler)(void))
{
+ WARN_ON(work_pending(&floppy_work));
+
PREPARE_WORK(&floppy_work, (work_func_t)handler);
- schedule_work(&floppy_work);
+ queue_work(system_nrt_wq, &floppy_work);
}

-static DEFINE_TIMER(fd_timer, NULL, 0, 0);
+static DECLARE_DELAYED_WORK(fd_timer, NULL);

static void cancel_activity(void)
{
- unsigned long flags;
-
- spin_lock_irqsave(&floppy_lock, flags);
do_floppy = NULL;
- PREPARE_WORK(&floppy_work, (work_func_t)empty);
- del_timer(&fd_timer);
- spin_unlock_irqrestore(&floppy_lock, flags);
+ cancel_delayed_work_sync(&fd_timer);
+ cancel_work_sync(&floppy_work);
}

/* this function makes sure that the disk stays in the drive during the
* transfer */
-static void fd_watchdog(void)
+static void fd_watchdog(struct work_struct *arg)
{
debug_dcl(DP->flags, "calling disk change from watchdog\n");

@@ -997,21 +989,21 @@ static void fd_watchdog(void)
cont->done(0);
reset_fdc();
} else {
- del_timer(&fd_timer);
- fd_timer.function = (timeout_fn)fd_watchdog;
- fd_timer.expires = jiffies + HZ / 10;
- add_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, fd_watchdog);
+ queue_delayed_work(system_nrt_wq,
+ &fd_timer, HZ / 10);
}
}

static void main_command_interrupt(void)
{
- del_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
cont->interrupt();
}

/* waits for a delay (spinup or select) to pass */
-static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
+static int fd_wait_for_completion(unsigned long expires, work_func_t function)
{
if (FDCS->reset) {
reset_fdc(); /* do the reset during sleep to win time
@@ -1020,11 +1012,10 @@ static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
return 1;
}

- if (time_before(jiffies, delay)) {
- del_timer(&fd_timer);
- fd_timer.function = function;
- fd_timer.expires = delay;
- add_timer(&fd_timer);
+ if (time_before(jiffies, expires)) {
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, function);
+ queue_delayed_work(system_nrt_wq, &fd_timer, expires - jiffies);
return 1;
}
return 0;
@@ -1342,7 +1333,7 @@ static int fdc_dtr(void)
*/
FDCS->dtr = raw_cmd->rate & 3;
return fd_wait_for_completion(jiffies + 2UL * HZ / 100,
- (timeout_fn)floppy_ready);
+ (work_func_t)floppy_ready);
} /* fdc_dtr */

static void tell_sector(void)
@@ -1447,7 +1438,7 @@ static void setup_rw_floppy(void)
int flags;
int dflags;
unsigned long ready_date;
- timeout_fn function;
+ work_func_t function;

flags = raw_cmd->flags;
if (flags & (FD_RAW_READ | FD_RAW_WRITE))
@@ -1461,9 +1452,9 @@ static void setup_rw_floppy(void)
*/
if (time_after(ready_date, jiffies + DP->select_delay)) {
ready_date -= DP->select_delay;
- function = (timeout_fn)floppy_start;
+ function = (work_func_t)floppy_start;
} else
- function = (timeout_fn)setup_rw_floppy;
+ function = (work_func_t)setup_rw_floppy;

/* wait until the floppy is spinning fast enough */
if (fd_wait_for_completion(ready_date, function))
@@ -1493,7 +1484,7 @@ static void setup_rw_floppy(void)
inr = result();
cont->interrupt();
} else if (flags & FD_RAW_NEED_DISK)
- fd_watchdog();
+ fd_watchdog(NULL);
}

static int blind_seek;
@@ -1802,20 +1793,22 @@ static void show_floppy(void)
pr_info("do_floppy=%pf\n", do_floppy);
if (work_pending(&floppy_work))
pr_info("floppy_work.func=%pf\n", floppy_work.func);
- if (timer_pending(&fd_timer))
- pr_info("fd_timer.function=%pf\n", fd_timer.function);
- if (timer_pending(&fd_timeout)) {
- pr_info("timer_function=%pf\n", fd_timeout.function);
- pr_info("expires=%lu\n", fd_timeout.expires - jiffies);
- pr_info("now=%lu\n", jiffies);
- }
+ if (delayed_work_pending(&fd_timer))
+ pr_info("delayed work.function=%p expires=%ld\n",
+ fd_timer.work.func,
+ fd_timer.timer.expires - jiffies);
+ if (delayed_work_pending(&fd_timeout))
+ pr_info("timer_function=%p expires=%ld\n",
+ fd_timeout.work.func,
+ fd_timeout.timer.expires - jiffies);
+
pr_info("cont=%p\n", cont);
pr_info("current_req=%p\n", current_req);
pr_info("command_status=%d\n", command_status);
pr_info("\n");
}

-static void floppy_shutdown(unsigned long data)
+static void floppy_shutdown(struct work_struct *arg)
{
unsigned long flags;

@@ -1868,7 +1861,7 @@ static int start_motor(void (*function)(void))

/* wait_for_completion also schedules reset if needed. */
return fd_wait_for_completion(DRS->select_date + DP->select_delay,
- (timeout_fn)function);
+ (work_func_t)function);
}

static void floppy_ready(void)
@@ -2821,7 +2814,6 @@ do_request:
spin_lock_irq(&floppy_lock);
pending = set_next_request();
spin_unlock_irq(&floppy_lock);
-
if (!pending) {
do_floppy = NULL;
unlock_fdc();
@@ -2898,13 +2890,15 @@ static void do_fd_request(struct request_queue *q)
current_req->cmd_flags))
return;

- if (test_bit(0, &fdc_busy)) {
+ if (test_and_set_bit(0, &fdc_busy)) {
/* fdc busy, this new request will be treated when the
current one is done */
is_alive(__func__, "old request running");
return;
}
- lock_fdc(MAXTIMEOUT, false);
+ command_status = FD_COMMAND_NONE;
+ __reschedule_timeout(MAXTIMEOUT, "fd_request");
+ set_fdc(0);
process_fd_request();
is_alive(__func__, "");
}
@@ -4213,7 +4207,7 @@ static int __init floppy_init(void)
use_virtual_dma = can_use_virtual_dma & 1;
fdc_state[0].address = FDC1;
if (fdc_state[0].address == -1) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -ENODEV;
goto out_unreg_region;
}
@@ -4224,7 +4218,7 @@ static int __init floppy_init(void)
fdc = 0; /* reset fdc in case of unexpected interrupt */
err = floppy_grab_irq_and_dma();
if (err) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -EBUSY;
goto out_unreg_region;
}
@@ -4281,13 +4275,13 @@ static int __init floppy_init(void)
user_reset_fdc(-1, FD_RESET_ALWAYS, false);
}
fdc = 0;
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
current_drive = 0;
initialized = true;
if (have_no_fdc) {
DPRINT("no floppy controllers found\n");
err = have_no_fdc;
- goto out_flush_work;
+ goto out_release_dma;
}

for (drive = 0; drive < N_DRIVE; drive++) {
@@ -4302,7 +4296,7 @@ static int __init floppy_init(void)

err = platform_device_register(&floppy_device[drive]);
if (err)
- goto out_flush_work;
+ goto out_release_dma;

err = device_create_file(&floppy_device[drive].dev,
&dev_attr_cmos);
@@ -4320,8 +4314,7 @@ static int __init floppy_init(void)

out_unreg_platform_dev:
platform_device_unregister(&floppy_device[drive]);
-out_flush_work:
- flush_work_sync(&floppy_work);
+out_release_dma:
if (atomic_read(&usage_count))
floppy_release_irq_and_dma();
out_unreg_region:
@@ -4397,7 +4390,7 @@ static int floppy_grab_irq_and_dma(void)
* We might have scheduled a free_irq(), wait it to
* drain first:
*/
- flush_work_sync(&floppy_work);
+ flush_workqueue(system_nrt_wq);

if (fd_request_irq()) {
DPRINT("Unable to grab IRQ%d for the floppy driver\n",
@@ -4488,9 +4481,9 @@ static void floppy_release_irq_and_dma(void)
pr_info("motor off timer %d still active\n", drive);
#endif

- if (timer_pending(&fd_timeout))
+ if (delayed_work_pending(&fd_timeout))
pr_info("floppy timer still active:%s\n", timeout_message);
- if (timer_pending(&fd_timer))
+ if (delayed_work_pending(&fd_timer))
pr_info("auxiliary floppy timer still active\n");
if (work_pending(&floppy_work))
pr_info("work still pending\n");
@@ -4560,8 +4553,9 @@ static void __exit floppy_module_exit(void)
put_disk(disks[drive]);
}

- del_timer_sync(&fd_timeout);
- del_timer_sync(&fd_timer);
+ cancel_delayed_work_sync(&fd_timeout);
+ cancel_delayed_work_sync(&fd_timer);
+ flush_workqueue(system_nrt_wq);

if (atomic_read(&usage_count))
floppy_release_irq_and_dma();

--
Jiri Kosina
SUSE Labs

2012-05-16 19:53:38

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 12:47:27PM -0700, Linus Torvalds wrote:
> On Wed, May 16, 2012 at 12:37 PM, Jiri Kosina <[email protected]> wrote:
> > + ? ? ? cancel_delayed_work_sync(&fd_timeout);
> > + ? ? ? cancel_delayed_work_sync(&fd_timer);
> > + ? ? ? destroy_workqueue(system_nrt_wq);
>
> Well, *that* doesn't look right.
>
> I think just a "flush_workqueue()" is in order.

System wqs shouldn't be flushed (nothing guarantees that flush will
finish in fixed amount of time). We probably should make that
explicit by whining when someone tries to flush one of the system wqs.
Here, the two cancel_delayed_work_sync() calls should be enough.

Thanks.

--
tejun

2012-05-16 19:57:32

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, 16 May 2012, Tejun Heo wrote:

> > On Wed, May 16, 2012 at 12:37 PM, Jiri Kosina <[email protected]> wrote:
> > > + ? ? ? cancel_delayed_work_sync(&fd_timeout);
> > > + ? ? ? cancel_delayed_work_sync(&fd_timer);
> > > + ? ? ? destroy_workqueue(system_nrt_wq);
> >
> > Well, *that* doesn't look right.
> >
> > I think just a "flush_workqueue()" is in order.
>
> System wqs shouldn't be flushed (nothing guarantees that flush will
> finish in fixed amount of time). We probably should make that
> explicit by whining when someone tries to flush one of the system wqs.
> Here, the two cancel_delayed_work_sync() calls should be enough.

Well, then I think this might be an issue for straightforward conversion
of floppy driver to system_nrt_wq -- see floppy_grab_irq_and_dma().

So perhaps going back to the original version with separate
single-threaded workqueue would be a reasonable idea.

--
Jiri Kosina
SUSE Labs

2012-05-16 20:01:54

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 09:57:22PM +0200, Jiri Kosina wrote:
> On Wed, 16 May 2012, Tejun Heo wrote:
>
> > > On Wed, May 16, 2012 at 12:37 PM, Jiri Kosina <[email protected]> wrote:
> > > > + ? ? ? cancel_delayed_work_sync(&fd_timeout);
> > > > + ? ? ? cancel_delayed_work_sync(&fd_timer);
> > > > + ? ? ? destroy_workqueue(system_nrt_wq);
> > >
> > > Well, *that* doesn't look right.
> > >
> > > I think just a "flush_workqueue()" is in order.
> >
> > System wqs shouldn't be flushed (nothing guarantees that flush will
> > finish in fixed amount of time). We probably should make that
> > explicit by whining when someone tries to flush one of the system wqs.
> > Here, the two cancel_delayed_work_sync() calls should be enough.
>
> Well, then I think this might be an issue for straightforward conversion
> of floppy driver to system_nrt_wq -- see floppy_grab_irq_and_dma().

Hmmm? flush_work() is fine. flush_workqueue() isn't. Am I missing
something?

--
tejun

2012-05-16 20:24:44

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, 16 May 2012, Tejun Heo wrote:

> > > > On Wed, May 16, 2012 at 12:37 PM, Jiri Kosina <[email protected]> wrote:
> > > > > + ? ? ? cancel_delayed_work_sync(&fd_timeout);
> > > > > + ? ? ? cancel_delayed_work_sync(&fd_timer);
> > > > > + ? ? ? destroy_workqueue(system_nrt_wq);
> > > >
> > > > Well, *that* doesn't look right.
> > > >
> > > > I think just a "flush_workqueue()" is in order.
> > >
> > > System wqs shouldn't be flushed (nothing guarantees that flush will
> > > finish in fixed amount of time). We probably should make that
> > > explicit by whining when someone tries to flush one of the system wqs.
> > > Here, the two cancel_delayed_work_sync() calls should be enough.
> >
> > Well, then I think this might be an issue for straightforward conversion
> > of floppy driver to system_nrt_wq -- see floppy_grab_irq_and_dma().
>
> Hmmm? flush_work() is fine. flush_workqueue() isn't. Am I missing
> something?

In floppy_grab_irq_and_dma() the point is to drain the workqueue
completely (before the conversion, we were just using
flush_work_sync(&floppy_work) for particular work item), and for that
flush_work() is not sufficient any more.

So I am really considering going back to driver-specific singlethreaded
workqueue.

Thanks,

--
Jiri Kosina
SUSE Labs

2012-05-16 20:29:30

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 10:24:37PM +0200, Jiri Kosina wrote:
> In floppy_grab_irq_and_dma() the point is to drain the workqueue
> completely (before the conversion, we were just using
> flush_work_sync(&floppy_work) for particular work item), and for that
> flush_work() is not sufficient any more.
>
> So I am really considering going back to driver-specific singlethreaded
> workqueue.

Ummm... still confused. flush_work_sync() is fine too. If you have
two, two calls to flush_work_sync() are equivalent to flushing the
workqueue in effect. You just need to avoid flush_workqueue() because
system workqueues may be hosting work items which can run arbitrarily
long.

Thanks.

--
tejun

2012-05-16 20:43:01

by Linus Torvalds

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 1:29 PM, Tejun Heo <[email protected]> wrote:
>
> Ummm... still confused. ?flush_work_sync() is fine too. ?If you have
> two, two calls to flush_work_sync() are equivalent to flushing the
> workqueue in effect. ?You just need to avoid flush_workqueue() because
> system workqueues may be hosting work items which can run arbitrarily
> long.

Umm. If there are abritrarily long things and these are serialized,
then that workqueue is not good for putting floppy work on it either,
is it? I don't think you can have it both ways.

Either it's "good enough" for putting floppy_work, fd_timeout and
fd_timer on, or it's not. If it's good enough, then flush_workqueue()
should damn well be timely enough. And if flush_workqueue() isn't
timely enough, then it doesn't sound like system_nrt_wq is the wrong
choice.

Linus

2012-05-16 20:44:42

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, 16 May 2012, Tejun Heo wrote:

> > In floppy_grab_irq_and_dma() the point is to drain the workqueue
> > completely (before the conversion, we were just using
> > flush_work_sync(&floppy_work) for particular work item), and for that
> > flush_work() is not sufficient any more.
> >
> > So I am really considering going back to driver-specific singlethreaded
> > workqueue.
>
> Ummm... still confused. flush_work_sync() is fine too. If you have
> two, two calls to flush_work_sync() are equivalent to flushing the
> workqueue in effect. You just need to avoid flush_workqueue() because
> system workqueues may be hosting work items which can run arbitrarily
> long.

Before the conversion, we do

flush_work_sync(&floppy_work);

in floppy_grab_irq_and_dma(). After the conversion, the single-threaded
workqueue is used to queue more than just floppy_work, and we want all
this to be flushed before proceeding, so neither flush_work() nor
flush_work_sync() is enough, as there might be floppy_work, fd_timer or
fd_timeout queued. This all has to be flushed.

If this still doesn't seem to make sense, I'll get back to it tomorrow, it
might be just too late and my brain cells might already be dreaming.

--
Jiri Kosina
SUSE Labs

2012-05-17 07:57:14

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

So after the discussion yesterday I decided to go back to the separate
single-threaded workqueue just for the purpose of the floppy driver.
Please find below the patch I consider final for 3.5 inclusion (and can
even push it to stable afterwards as well). Linus' Acked-by added, ok?

Thanks.



From: Jiri Kosina <[email protected]>
Subject: [PATCH] floppy: convert to delayed work and single-thread wq

There are several races in floppy driver between bottom half
(scheduled_work) and timers (fd_timeout, fd_timer). Due to slowness
of the actual floppy devices, those races are never (at least to my
knowledge) triggered on a bare floppy metal. However on virtualized
(emulated) floppy drives, which are of course magnitudes faster
than the real ones, these races trigger reliably. They usually exhibit
themselves as NULL pointer dereferences during DMA setup, such as

BUG: unable to handle kernel NULL pointer dereference at 0000000a
[ ... snip ... ]
EIP: 0060:[<c02053d5>] EFLAGS: 00010293 CPU: 0
EAX: ffffe000 EBX: 0000000a ECX: 00000000 EDX: 0000000a
ESI: c05d2718 EDI: 00000000 EBP: 00000000 ESP: f540fe44
DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068
Process swapper (pid: 0, ti=f540e000 task=c082d5a0 task.ti=c0826000)
Stack:
ffffe000 00001ffc 00000000 00000000 00000000 c05d2718 c0708b40 f540fe80
c020470f c05d2718 c0708b40 00000000 f540fe80 0000000a f540fee4 00000000
c0708b40 f540fee4 00000000 00000000 c020526b 00000000 c05d2718 c0708b40
Call Trace:
[<c020470f>] dump_trace+0xaf/0x110
[<c020526b>] show_trace_log_lvl+0x4b/0x60
[<c0205298>] show_trace+0x18/0x20
[<c05c5811>] dump_stack+0x6d/0x72
[<c0248527>] warn_slowpath_common+0x77/0xb0
[<c02485f3>] warn_slowpath_fmt+0x33/0x40
[<f7ec593c>] setup_DMA+0x14c/0x210 [floppy]
[<f7ecaa95>] setup_rw_floppy+0x105/0x190 [floppy]
[<c0256d08>] run_timer_softirq+0x168/0x2a0
[<c024e762>] __do_softirq+0xc2/0x1c0
[<c02042ed>] do_softirq+0x7d/0xb0
[<f54d8a00>] 0xf54d89ff

but other instances can be easily seen as well. This can be observed at least under
VMWare, VirtualBox and KVM.

This patch converts all the timers and bottom halfs to be processed in a single
workqueue. This aproach has been already discussed back in 2010 if I remember
correctly, and Acked by Linus [1], but it then never made it to the tree.

This all is based on original idea and code of Stephen Hemminger. I have
ported original Stepen's code to the current state of the floppy driver, and
performed quite some testing (on real hardware), which didn't reveal any issues
(this includes not only writing and reading data, but also formatting
(unfortunately I didn't find any Double-Density disks any more)). Ability to
handle errors properly (supplying known bad floppies) has also been verified.

[1] http://kerneltrap.org/mailarchive/linux-kernel/2010/6/11/4582092

Based-on-patch-by: Stephen Hemminger <[email protected]>
Acked-by: Linus Torvalds <[email protected]>
Signed-off-by: Jiri Kosina <[email protected]>
---
drivers/block/floppy.c | 143 ++++++++++++++++++++++++-----------------------
1 files changed, 73 insertions(+), 70 deletions(-)

diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c
index b0b00d7..f612626 100644
--- a/drivers/block/floppy.c
+++ b/drivers/block/floppy.c
@@ -551,7 +551,7 @@ static void floppy_ready(void);
static void floppy_start(void);
static void process_fd_request(void);
static void recalibrate_floppy(void);
-static void floppy_shutdown(unsigned long);
+static void floppy_shutdown(struct work_struct *);

static int floppy_request_regions(int);
static void floppy_release_regions(int);
@@ -588,6 +588,8 @@ static int buffer_max = -1;
static struct floppy_fdc_state fdc_state[N_FDC];
static int fdc; /* current fdc */

+static struct workqueue_struct *floppy_wq;
+
static struct floppy_struct *_floppy = floppy_type;
static unsigned char current_drive;
static long current_count_sectors;
@@ -629,16 +631,15 @@ static inline void set_debugt(void) { }
static inline void debugt(const char *func, const char *msg) { }
#endif /* DEBUGT */

-typedef void (*timeout_fn)(unsigned long);
-static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0);

+static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown);
static const char *timeout_message;

static void is_alive(const char *func, const char *message)
{
/* this routine checks whether the floppy driver is "alive" */
if (test_bit(0, &fdc_busy) && command_status < 2 &&
- !timer_pending(&fd_timeout)) {
+ !delayed_work_pending(&fd_timeout)) {
DPRINT("%s: timeout handler died. %s\n", func, message);
}
}
@@ -666,15 +667,18 @@ static int output_log_pos;

static void __reschedule_timeout(int drive, const char *message)
{
+ unsigned long delay;
+
if (drive == current_reqD)
drive = current_drive;
- del_timer(&fd_timeout);
+
if (drive < 0 || drive >= N_DRIVE) {
- fd_timeout.expires = jiffies + 20UL * HZ;
+ delay = 20UL * HZ;
drive = 0;
} else
- fd_timeout.expires = jiffies + UDP->timeout;
- add_timer(&fd_timeout);
+ delay = UDP->timeout;
+
+ queue_delayed_work(floppy_wq, &fd_timeout, delay);
if (UDP->flags & FD_DEBUG)
DPRINT("reschedule timeout %s\n", message);
timeout_message = message;
@@ -872,7 +876,7 @@ static int lock_fdc(int drive, bool interruptible)

command_status = FD_COMMAND_NONE;

- __reschedule_timeout(drive, "lock fdc");
+ reschedule_timeout(drive, "lock fdc");
set_fdc(drive);
return 0;
}
@@ -880,23 +884,15 @@ static int lock_fdc(int drive, bool interruptible)
/* unlocks the driver */
static void unlock_fdc(void)
{
- unsigned long flags;
-
- raw_cmd = NULL;
if (!test_bit(0, &fdc_busy))
DPRINT("FDC access conflict!\n");

- if (do_floppy)
- DPRINT("device interrupt still active at FDC release: %pf!\n",
- do_floppy);
+ raw_cmd = NULL;
command_status = FD_COMMAND_NONE;
- spin_lock_irqsave(&floppy_lock, flags);
- del_timer(&fd_timeout);
+ __cancel_delayed_work(&fd_timeout);
+ do_floppy = NULL;
cont = NULL;
clear_bit(0, &fdc_busy);
- if (current_req || set_next_request())
- do_fd_request(current_req->q);
- spin_unlock_irqrestore(&floppy_lock, flags);
wake_up(&fdc_wait);
}

@@ -968,26 +964,24 @@ static DECLARE_WORK(floppy_work, NULL);

static void schedule_bh(void (*handler)(void))
{
+ WARN_ON(work_pending(&floppy_work));
+
PREPARE_WORK(&floppy_work, (work_func_t)handler);
- schedule_work(&floppy_work);
+ queue_work(floppy_wq, &floppy_work);
}

-static DEFINE_TIMER(fd_timer, NULL, 0, 0);
+static DECLARE_DELAYED_WORK(fd_timer, NULL);

static void cancel_activity(void)
{
- unsigned long flags;
-
- spin_lock_irqsave(&floppy_lock, flags);
do_floppy = NULL;
- PREPARE_WORK(&floppy_work, (work_func_t)empty);
- del_timer(&fd_timer);
- spin_unlock_irqrestore(&floppy_lock, flags);
+ cancel_delayed_work_sync(&fd_timer);
+ cancel_work_sync(&floppy_work);
}

/* this function makes sure that the disk stays in the drive during the
* transfer */
-static void fd_watchdog(void)
+static void fd_watchdog(struct work_struct *arg)
{
debug_dcl(DP->flags, "calling disk change from watchdog\n");

@@ -997,21 +991,20 @@ static void fd_watchdog(void)
cont->done(0);
reset_fdc();
} else {
- del_timer(&fd_timer);
- fd_timer.function = (timeout_fn)fd_watchdog;
- fd_timer.expires = jiffies + HZ / 10;
- add_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, fd_watchdog);
+ queue_delayed_work(floppy_wq, &fd_timer, HZ / 10);
}
}

static void main_command_interrupt(void)
{
- del_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
cont->interrupt();
}

/* waits for a delay (spinup or select) to pass */
-static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
+static int fd_wait_for_completion(unsigned long expires, work_func_t function)
{
if (FDCS->reset) {
reset_fdc(); /* do the reset during sleep to win time
@@ -1020,11 +1013,10 @@ static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
return 1;
}

- if (time_before(jiffies, delay)) {
- del_timer(&fd_timer);
- fd_timer.function = function;
- fd_timer.expires = delay;
- add_timer(&fd_timer);
+ if (time_before(jiffies, expires)) {
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, function);
+ queue_delayed_work(floppy_wq, &fd_timer, expires - jiffies);
return 1;
}
return 0;
@@ -1342,7 +1334,7 @@ static int fdc_dtr(void)
*/
FDCS->dtr = raw_cmd->rate & 3;
return fd_wait_for_completion(jiffies + 2UL * HZ / 100,
- (timeout_fn)floppy_ready);
+ (work_func_t)floppy_ready);
} /* fdc_dtr */

static void tell_sector(void)
@@ -1447,7 +1439,7 @@ static void setup_rw_floppy(void)
int flags;
int dflags;
unsigned long ready_date;
- timeout_fn function;
+ work_func_t function;

flags = raw_cmd->flags;
if (flags & (FD_RAW_READ | FD_RAW_WRITE))
@@ -1461,9 +1453,9 @@ static void setup_rw_floppy(void)
*/
if (time_after(ready_date, jiffies + DP->select_delay)) {
ready_date -= DP->select_delay;
- function = (timeout_fn)floppy_start;
+ function = (work_func_t)floppy_start;
} else
- function = (timeout_fn)setup_rw_floppy;
+ function = (work_func_t)setup_rw_floppy;

/* wait until the floppy is spinning fast enough */
if (fd_wait_for_completion(ready_date, function))
@@ -1493,7 +1485,7 @@ static void setup_rw_floppy(void)
inr = result();
cont->interrupt();
} else if (flags & FD_RAW_NEED_DISK)
- fd_watchdog();
+ fd_watchdog(NULL);
}

static int blind_seek;
@@ -1802,20 +1794,22 @@ static void show_floppy(void)
pr_info("do_floppy=%pf\n", do_floppy);
if (work_pending(&floppy_work))
pr_info("floppy_work.func=%pf\n", floppy_work.func);
- if (timer_pending(&fd_timer))
- pr_info("fd_timer.function=%pf\n", fd_timer.function);
- if (timer_pending(&fd_timeout)) {
- pr_info("timer_function=%pf\n", fd_timeout.function);
- pr_info("expires=%lu\n", fd_timeout.expires - jiffies);
- pr_info("now=%lu\n", jiffies);
- }
+ if (delayed_work_pending(&fd_timer))
+ pr_info("delayed work.function=%p expires=%ld\n",
+ fd_timer.work.func,
+ fd_timer.timer.expires - jiffies);
+ if (delayed_work_pending(&fd_timeout))
+ pr_info("timer_function=%p expires=%ld\n",
+ fd_timeout.work.func,
+ fd_timeout.timer.expires - jiffies);
+
pr_info("cont=%p\n", cont);
pr_info("current_req=%p\n", current_req);
pr_info("command_status=%d\n", command_status);
pr_info("\n");
}

-static void floppy_shutdown(unsigned long data)
+static void floppy_shutdown(struct work_struct *arg)
{
unsigned long flags;

@@ -1868,7 +1862,7 @@ static int start_motor(void (*function)(void))

/* wait_for_completion also schedules reset if needed. */
return fd_wait_for_completion(DRS->select_date + DP->select_delay,
- (timeout_fn)function);
+ (work_func_t)function);
}

static void floppy_ready(void)
@@ -2821,7 +2815,6 @@ do_request:
spin_lock_irq(&floppy_lock);
pending = set_next_request();
spin_unlock_irq(&floppy_lock);
-
if (!pending) {
do_floppy = NULL;
unlock_fdc();
@@ -2898,13 +2891,15 @@ static void do_fd_request(struct request_queue *q)
current_req->cmd_flags))
return;

- if (test_bit(0, &fdc_busy)) {
+ if (test_and_set_bit(0, &fdc_busy)) {
/* fdc busy, this new request will be treated when the
current one is done */
is_alive(__func__, "old request running");
return;
}
- lock_fdc(MAXTIMEOUT, false);
+ command_status = FD_COMMAND_NONE;
+ __reschedule_timeout(MAXTIMEOUT, "fd_request");
+ set_fdc(0);
process_fd_request();
is_alive(__func__, "");
}
@@ -4159,10 +4154,16 @@ static int __init floppy_init(void)
goto out_put_disk;
}

+ floppy_wq = create_singlethread_workqueue("floppy");
+ if (!floppy_wq) {
+ err = -ENOMEM;
+ goto out_put_disk;
+ }
+
disks[dr]->queue = blk_init_queue(do_fd_request, &floppy_lock);
if (!disks[dr]->queue) {
err = -ENOMEM;
- goto out_put_disk;
+ goto out_destroy_workq;
}

blk_queue_max_hw_sectors(disks[dr]->queue, 64);
@@ -4213,7 +4214,7 @@ static int __init floppy_init(void)
use_virtual_dma = can_use_virtual_dma & 1;
fdc_state[0].address = FDC1;
if (fdc_state[0].address == -1) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -ENODEV;
goto out_unreg_region;
}
@@ -4224,7 +4225,7 @@ static int __init floppy_init(void)
fdc = 0; /* reset fdc in case of unexpected interrupt */
err = floppy_grab_irq_and_dma();
if (err) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -EBUSY;
goto out_unreg_region;
}
@@ -4281,13 +4282,13 @@ static int __init floppy_init(void)
user_reset_fdc(-1, FD_RESET_ALWAYS, false);
}
fdc = 0;
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
current_drive = 0;
initialized = true;
if (have_no_fdc) {
DPRINT("no floppy controllers found\n");
err = have_no_fdc;
- goto out_flush_work;
+ goto out_release_dma;
}

for (drive = 0; drive < N_DRIVE; drive++) {
@@ -4302,7 +4303,7 @@ static int __init floppy_init(void)

err = platform_device_register(&floppy_device[drive]);
if (err)
- goto out_flush_work;
+ goto out_release_dma;

err = device_create_file(&floppy_device[drive].dev,
&dev_attr_cmos);
@@ -4320,13 +4321,14 @@ static int __init floppy_init(void)

out_unreg_platform_dev:
platform_device_unregister(&floppy_device[drive]);
-out_flush_work:
- flush_work_sync(&floppy_work);
+out_release_dma:
if (atomic_read(&usage_count))
floppy_release_irq_and_dma();
out_unreg_region:
blk_unregister_region(MKDEV(FLOPPY_MAJOR, 0), 256);
platform_driver_unregister(&floppy_driver);
+out_destroy_workq:
+ destroy_workqueue(floppy_wq);
out_unreg_blkdev:
unregister_blkdev(FLOPPY_MAJOR, "fd");
out_put_disk:
@@ -4397,7 +4399,7 @@ static int floppy_grab_irq_and_dma(void)
* We might have scheduled a free_irq(), wait it to
* drain first:
*/
- flush_work_sync(&floppy_work);
+ flush_workqueue(floppy_wq);

if (fd_request_irq()) {
DPRINT("Unable to grab IRQ%d for the floppy driver\n",
@@ -4488,9 +4490,9 @@ static void floppy_release_irq_and_dma(void)
pr_info("motor off timer %d still active\n", drive);
#endif

- if (timer_pending(&fd_timeout))
+ if (delayed_work_pending(&fd_timeout))
pr_info("floppy timer still active:%s\n", timeout_message);
- if (timer_pending(&fd_timer))
+ if (delayed_work_pending(&fd_timer))
pr_info("auxiliary floppy timer still active\n");
if (work_pending(&floppy_work))
pr_info("work still pending\n");
@@ -4560,8 +4562,9 @@ static void __exit floppy_module_exit(void)
put_disk(disks[drive]);
}

- del_timer_sync(&fd_timeout);
- del_timer_sync(&fd_timer);
+ cancel_delayed_work_sync(&fd_timeout);
+ cancel_delayed_work_sync(&fd_timer);
+ destroy_workqueue(floppy_wq);

if (atomic_read(&usage_count))
floppy_release_irq_and_dma();
--
Jiri Kosina
SUSE Labs

2012-05-17 14:55:29

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

Hello, Linus.

On Wed, May 16, 2012 at 01:42:36PM -0700, Linus Torvalds wrote:
> On Wed, May 16, 2012 at 1:29 PM, Tejun Heo <[email protected]> wrote:
> >
> > Ummm... still confused. ?flush_work_sync() is fine too. ?If you have
> > two, two calls to flush_work_sync() are equivalent to flushing the
> > workqueue in effect. ?You just need to avoid flush_workqueue() because
> > system workqueues may be hosting work items which can run arbitrarily
> > long.
>
> Umm. If there are abritrarily long things and these are serialized,
> then that workqueue is not good for putting floppy work on it either,
> is it? I don't think you can have it both ways.

They're not being serialized. system_nrt_wq like any other system wqs
has 256 as max_active - so upto 256 work items per cpu can be
executing (ie. assigned to an active worker) at any given time and
because it's a large shared pool, some of them are allowed to take
long time.

> Either it's "good enough" for putting floppy_work, fd_timeout and
> fd_timer on, or it's not. If it's good enough, then flush_workqueue()
> should damn well be timely enough. And if flush_workqueue() isn't
> timely enough, then it doesn't sound like system_nrt_wq is the wrong
> choice.

So, per-work item, it's timely enough. It shouldn't be different from
using a dedicated workqueue. Different work items don't interact with
each other differently whether they belong to the same workqueue or
different ones as long as the workqueue's max_active limit isn't
reached. flush_workqueue() is a different story as it forces all work
items belonging to the workqueue to be related. That's why one of the
valid reasons to create a workqueue is to have a separate flush
(workqueue) domain - e.g. when the caller doesn't know which work
items need to be flushed because they're dynamically allocated and
freed on work item execution.

Thanks.

--
tejun

2012-05-17 15:04:25

by Linus Torvalds

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Thu, May 17, 2012 at 7:55 AM, Tejun Heo <[email protected]> wrote:
>
> They're not being serialized.

If they aren't serialized, they are useless for floppy for other
reasons. There are *three* work item for the floppy driver, and the
whole point was to serialize them.

Linus

2012-05-17 15:06:49

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Thu, 17 May 2012, Tejun Heo wrote:

> > Umm. If there are abritrarily long things and these are serialized,
> > then that workqueue is not good for putting floppy work on it either,
> > is it? I don't think you can have it both ways.
>
> They're not being serialized.

Then it's useless for this very purpose -- the sole purpose of the
floppy_wq having as a single-threaded wq is to run all the work
serialized.

So the patch I have sent out this morning is the way to go.

Thanks,

--
Jiri Kosina
SUSE Labs

2012-05-17 15:06:58

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Wed, May 16, 2012 at 10:44:31PM +0200, Jiri Kosina wrote:
> On Wed, 16 May 2012, Tejun Heo wrote:
>
> > > In floppy_grab_irq_and_dma() the point is to drain the workqueue
> > > completely (before the conversion, we were just using
> > > flush_work_sync(&floppy_work) for particular work item), and for that
> > > flush_work() is not sufficient any more.
> > >
> > > So I am really considering going back to driver-specific singlethreaded
> > > workqueue.
> >
> > Ummm... still confused. flush_work_sync() is fine too. If you have
> > two, two calls to flush_work_sync() are equivalent to flushing the
> > workqueue in effect. You just need to avoid flush_workqueue() because
> > system workqueues may be hosting work items which can run arbitrarily
> > long.
>
> Before the conversion, we do
>
> flush_work_sync(&floppy_work);
>
> in floppy_grab_irq_and_dma(). After the conversion, the single-threaded
> workqueue is used to queue more than just floppy_work, and we want all
> this to be flushed before proceeding, so neither flush_work() nor
> flush_work_sync() is enough, as there might be floppy_work, fd_timer or
> fd_timeout queued. This all has to be flushed.
>
> If this still doesn't seem to make sense, I'll get back to it tomorrow, it
> might be just too late and my brain cells might already be dreaming.

So, AFAICS, there are only three work items in question here,
floppy_work, fd_timer and fd_timeout, so three calls to
flush[_delayed]_work_sync() is functionally equivalent to
flush_workqueue(). While flushing separately may take longer, AFAICS,
it doesn't seem to be on the hot path (not much in floppy can be
considered hot these days tho) and I don't think it would make any
noticeable performance difference.

If having a separate flush domain is beneficial, please create a
non-reentrant workqueue without rescuer - alloc_workqueue("floppy",
WQ_NON_REENTRANT, 0). If serialization among different work items is
necessary, single thread workqueue without rescuer can be used -
alloc_workqueue("floppy", WQ_UNBOUND, 1).

create_*workqueue() are left there for backwards compatibility and
they all have rescuer for that reason. Gotta clean them up and maybe
create different wrappers, I guess.

Thanks.

--
tejun

2012-05-17 15:09:31

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Thu, May 17, 2012 at 05:06:41PM +0200, Jiri Kosina wrote:
> On Thu, 17 May 2012, Tejun Heo wrote:
>
> > > Umm. If there are abritrarily long things and these are serialized,
> > > then that workqueue is not good for putting floppy work on it either,
> > > is it? I don't think you can have it both ways.
> >
> > They're not being serialized.
>
> Then it's useless for this very purpose -- the sole purpose of the
> floppy_wq having as a single-threaded wq is to run all the work
> serialized.
>
> So the patch I have sent out this morning is the way to go.

Yeah, that seems to be my confusion here - I thought serialization is
necessary only for a work item. Can you please use
alloc_ordered_workqueue() instead of create_singlethread_workqueue()?

Thanks.

--
tejun

2012-05-17 15:47:08

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

On Thu, 17 May 2012, Tejun Heo wrote:

> > > > Umm. If there are abritrarily long things and these are serialized,
> > > > then that workqueue is not good for putting floppy work on it either,
> > > > is it? I don't think you can have it both ways.
> > >
> > > They're not being serialized.
> >
> > Then it's useless for this very purpose -- the sole purpose of the
> > floppy_wq having as a single-threaded wq is to run all the work
> > serialized.
> >
> > So the patch I have sent out this morning is the way to go.
>
> Yeah, that seems to be my confusion here - I thought serialization is
> necessary only for a work item. Can you please use
> alloc_ordered_workqueue() instead of create_singlethread_workqueue()?

Well, seeing almost 200 occurences of create_singlethread_workqueue() (and
not talking about non-singlethread ones) doesn't really hint about this
interface being deprecated :) But whatever. The patch below uses
alloc_ordered_workqueue() instead. I have given it the same round of
testing, it passed.

Thanks.




From: Jiri Kosina <[email protected]>
Subject: [PATCH] floppy: convert to delayed work and single-thread wq

There are several races in floppy driver between bottom half
(scheduled_work) and timers (fd_timeout, fd_timer). Due to slowness
of the actual floppy devices, those races are never (at least to my
knowledge) triggered on a bare floppy metal. However on virtualized
(emulated) floppy drives, which are of course magnitudes faster
than the real ones, these races trigger reliably. They usually exhibit
themselves as NULL pointer dereferences during DMA setup, such as

BUG: unable to handle kernel NULL pointer dereference at 0000000a
[ ... snip ... ]
EIP: 0060:[<c02053d5>] EFLAGS: 00010293 CPU: 0
EAX: ffffe000 EBX: 0000000a ECX: 00000000 EDX: 0000000a
ESI: c05d2718 EDI: 00000000 EBP: 00000000 ESP: f540fe44
DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068
Process swapper (pid: 0, ti=f540e000 task=c082d5a0 task.ti=c0826000)
Stack:
ffffe000 00001ffc 00000000 00000000 00000000 c05d2718 c0708b40 f540fe80
c020470f c05d2718 c0708b40 00000000 f540fe80 0000000a f540fee4 00000000
c0708b40 f540fee4 00000000 00000000 c020526b 00000000 c05d2718 c0708b40
Call Trace:
[<c020470f>] dump_trace+0xaf/0x110
[<c020526b>] show_trace_log_lvl+0x4b/0x60
[<c0205298>] show_trace+0x18/0x20
[<c05c5811>] dump_stack+0x6d/0x72
[<c0248527>] warn_slowpath_common+0x77/0xb0
[<c02485f3>] warn_slowpath_fmt+0x33/0x40
[<f7ec593c>] setup_DMA+0x14c/0x210 [floppy]
[<f7ecaa95>] setup_rw_floppy+0x105/0x190 [floppy]
[<c0256d08>] run_timer_softirq+0x168/0x2a0
[<c024e762>] __do_softirq+0xc2/0x1c0
[<c02042ed>] do_softirq+0x7d/0xb0
[<f54d8a00>] 0xf54d89ff

but other instances can be easily seen as well. This can be observed at least under
VMWare, VirtualBox and KVM.

This patch converts all the timers and bottom halfs to be processed in a single
workqueue. This aproach has been already discussed back in 2010 if I remember
correctly, and Acked by Linus [1], but it then never made it to the tree.

This all is based on original idea and code of Stephen Hemminger. I have
ported original Stepen's code to the current state of the floppy driver, and
performed quite some testing (on real hardware), which didn't reveal any issues
(this includes not only writing and reading data, but also formatting
(unfortunately I didn't find any Double-Density disks any more)). Ability to
handle errors properly (supplying known bad floppies) has also been verified.

[1] http://kerneltrap.org/mailarchive/linux-kernel/2010/6/11/4582092

Based-on-patch-by: Stephen Hemminger <[email protected]>
Acked-by: Linus Torvalds <[email protected]>
Signed-off-by: Jiri Kosina <[email protected]>
---
drivers/block/floppy.c | 143 ++++++++++++++++++++++++-----------------------
1 files changed, 73 insertions(+), 70 deletions(-)

diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c
index b0b00d7..48e1d70 100644
--- a/drivers/block/floppy.c
+++ b/drivers/block/floppy.c
@@ -551,7 +551,7 @@ static void floppy_ready(void);
static void floppy_start(void);
static void process_fd_request(void);
static void recalibrate_floppy(void);
-static void floppy_shutdown(unsigned long);
+static void floppy_shutdown(struct work_struct *);

static int floppy_request_regions(int);
static void floppy_release_regions(int);
@@ -588,6 +588,8 @@ static int buffer_max = -1;
static struct floppy_fdc_state fdc_state[N_FDC];
static int fdc; /* current fdc */

+static struct workqueue_struct *floppy_wq;
+
static struct floppy_struct *_floppy = floppy_type;
static unsigned char current_drive;
static long current_count_sectors;
@@ -629,16 +631,15 @@ static inline void set_debugt(void) { }
static inline void debugt(const char *func, const char *msg) { }
#endif /* DEBUGT */

-typedef void (*timeout_fn)(unsigned long);
-static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0);

+static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown);
static const char *timeout_message;

static void is_alive(const char *func, const char *message)
{
/* this routine checks whether the floppy driver is "alive" */
if (test_bit(0, &fdc_busy) && command_status < 2 &&
- !timer_pending(&fd_timeout)) {
+ !delayed_work_pending(&fd_timeout)) {
DPRINT("%s: timeout handler died. %s\n", func, message);
}
}
@@ -666,15 +667,18 @@ static int output_log_pos;

static void __reschedule_timeout(int drive, const char *message)
{
+ unsigned long delay;
+
if (drive == current_reqD)
drive = current_drive;
- del_timer(&fd_timeout);
+
if (drive < 0 || drive >= N_DRIVE) {
- fd_timeout.expires = jiffies + 20UL * HZ;
+ delay = 20UL * HZ;
drive = 0;
} else
- fd_timeout.expires = jiffies + UDP->timeout;
- add_timer(&fd_timeout);
+ delay = UDP->timeout;
+
+ queue_delayed_work(floppy_wq, &fd_timeout, delay);
if (UDP->flags & FD_DEBUG)
DPRINT("reschedule timeout %s\n", message);
timeout_message = message;
@@ -872,7 +876,7 @@ static int lock_fdc(int drive, bool interruptible)

command_status = FD_COMMAND_NONE;

- __reschedule_timeout(drive, "lock fdc");
+ reschedule_timeout(drive, "lock fdc");
set_fdc(drive);
return 0;
}
@@ -880,23 +884,15 @@ static int lock_fdc(int drive, bool interruptible)
/* unlocks the driver */
static void unlock_fdc(void)
{
- unsigned long flags;
-
- raw_cmd = NULL;
if (!test_bit(0, &fdc_busy))
DPRINT("FDC access conflict!\n");

- if (do_floppy)
- DPRINT("device interrupt still active at FDC release: %pf!\n",
- do_floppy);
+ raw_cmd = NULL;
command_status = FD_COMMAND_NONE;
- spin_lock_irqsave(&floppy_lock, flags);
- del_timer(&fd_timeout);
+ __cancel_delayed_work(&fd_timeout);
+ do_floppy = NULL;
cont = NULL;
clear_bit(0, &fdc_busy);
- if (current_req || set_next_request())
- do_fd_request(current_req->q);
- spin_unlock_irqrestore(&floppy_lock, flags);
wake_up(&fdc_wait);
}

@@ -968,26 +964,24 @@ static DECLARE_WORK(floppy_work, NULL);

static void schedule_bh(void (*handler)(void))
{
+ WARN_ON(work_pending(&floppy_work));
+
PREPARE_WORK(&floppy_work, (work_func_t)handler);
- schedule_work(&floppy_work);
+ queue_work(floppy_wq, &floppy_work);
}

-static DEFINE_TIMER(fd_timer, NULL, 0, 0);
+static DECLARE_DELAYED_WORK(fd_timer, NULL);

static void cancel_activity(void)
{
- unsigned long flags;
-
- spin_lock_irqsave(&floppy_lock, flags);
do_floppy = NULL;
- PREPARE_WORK(&floppy_work, (work_func_t)empty);
- del_timer(&fd_timer);
- spin_unlock_irqrestore(&floppy_lock, flags);
+ cancel_delayed_work_sync(&fd_timer);
+ cancel_work_sync(&floppy_work);
}

/* this function makes sure that the disk stays in the drive during the
* transfer */
-static void fd_watchdog(void)
+static void fd_watchdog(struct work_struct *arg)
{
debug_dcl(DP->flags, "calling disk change from watchdog\n");

@@ -997,21 +991,20 @@ static void fd_watchdog(void)
cont->done(0);
reset_fdc();
} else {
- del_timer(&fd_timer);
- fd_timer.function = (timeout_fn)fd_watchdog;
- fd_timer.expires = jiffies + HZ / 10;
- add_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, fd_watchdog);
+ queue_delayed_work(floppy_wq, &fd_timer, HZ / 10);
}
}

static void main_command_interrupt(void)
{
- del_timer(&fd_timer);
+ cancel_delayed_work(&fd_timer);
cont->interrupt();
}

/* waits for a delay (spinup or select) to pass */
-static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
+static int fd_wait_for_completion(unsigned long expires, work_func_t function)
{
if (FDCS->reset) {
reset_fdc(); /* do the reset during sleep to win time
@@ -1020,11 +1013,10 @@ static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
return 1;
}

- if (time_before(jiffies, delay)) {
- del_timer(&fd_timer);
- fd_timer.function = function;
- fd_timer.expires = delay;
- add_timer(&fd_timer);
+ if (time_before(jiffies, expires)) {
+ cancel_delayed_work(&fd_timer);
+ PREPARE_DELAYED_WORK(&fd_timer, function);
+ queue_delayed_work(floppy_wq, &fd_timer, expires - jiffies);
return 1;
}
return 0;
@@ -1342,7 +1334,7 @@ static int fdc_dtr(void)
*/
FDCS->dtr = raw_cmd->rate & 3;
return fd_wait_for_completion(jiffies + 2UL * HZ / 100,
- (timeout_fn)floppy_ready);
+ (work_func_t)floppy_ready);
} /* fdc_dtr */

static void tell_sector(void)
@@ -1447,7 +1439,7 @@ static void setup_rw_floppy(void)
int flags;
int dflags;
unsigned long ready_date;
- timeout_fn function;
+ work_func_t function;

flags = raw_cmd->flags;
if (flags & (FD_RAW_READ | FD_RAW_WRITE))
@@ -1461,9 +1453,9 @@ static void setup_rw_floppy(void)
*/
if (time_after(ready_date, jiffies + DP->select_delay)) {
ready_date -= DP->select_delay;
- function = (timeout_fn)floppy_start;
+ function = (work_func_t)floppy_start;
} else
- function = (timeout_fn)setup_rw_floppy;
+ function = (work_func_t)setup_rw_floppy;

/* wait until the floppy is spinning fast enough */
if (fd_wait_for_completion(ready_date, function))
@@ -1493,7 +1485,7 @@ static void setup_rw_floppy(void)
inr = result();
cont->interrupt();
} else if (flags & FD_RAW_NEED_DISK)
- fd_watchdog();
+ fd_watchdog(NULL);
}

static int blind_seek;
@@ -1802,20 +1794,22 @@ static void show_floppy(void)
pr_info("do_floppy=%pf\n", do_floppy);
if (work_pending(&floppy_work))
pr_info("floppy_work.func=%pf\n", floppy_work.func);
- if (timer_pending(&fd_timer))
- pr_info("fd_timer.function=%pf\n", fd_timer.function);
- if (timer_pending(&fd_timeout)) {
- pr_info("timer_function=%pf\n", fd_timeout.function);
- pr_info("expires=%lu\n", fd_timeout.expires - jiffies);
- pr_info("now=%lu\n", jiffies);
- }
+ if (delayed_work_pending(&fd_timer))
+ pr_info("delayed work.function=%p expires=%ld\n",
+ fd_timer.work.func,
+ fd_timer.timer.expires - jiffies);
+ if (delayed_work_pending(&fd_timeout))
+ pr_info("timer_function=%p expires=%ld\n",
+ fd_timeout.work.func,
+ fd_timeout.timer.expires - jiffies);
+
pr_info("cont=%p\n", cont);
pr_info("current_req=%p\n", current_req);
pr_info("command_status=%d\n", command_status);
pr_info("\n");
}

-static void floppy_shutdown(unsigned long data)
+static void floppy_shutdown(struct work_struct *arg)
{
unsigned long flags;

@@ -1868,7 +1862,7 @@ static int start_motor(void (*function)(void))

/* wait_for_completion also schedules reset if needed. */
return fd_wait_for_completion(DRS->select_date + DP->select_delay,
- (timeout_fn)function);
+ (work_func_t)function);
}

static void floppy_ready(void)
@@ -2821,7 +2815,6 @@ do_request:
spin_lock_irq(&floppy_lock);
pending = set_next_request();
spin_unlock_irq(&floppy_lock);
-
if (!pending) {
do_floppy = NULL;
unlock_fdc();
@@ -2898,13 +2891,15 @@ static void do_fd_request(struct request_queue *q)
current_req->cmd_flags))
return;

- if (test_bit(0, &fdc_busy)) {
+ if (test_and_set_bit(0, &fdc_busy)) {
/* fdc busy, this new request will be treated when the
current one is done */
is_alive(__func__, "old request running");
return;
}
- lock_fdc(MAXTIMEOUT, false);
+ command_status = FD_COMMAND_NONE;
+ __reschedule_timeout(MAXTIMEOUT, "fd_request");
+ set_fdc(0);
process_fd_request();
is_alive(__func__, "");
}
@@ -4159,10 +4154,16 @@ static int __init floppy_init(void)
goto out_put_disk;
}

+ floppy_wq = alloc_ordered_workqueue("floppy", 0);
+ if (!floppy_wq) {
+ err = -ENOMEM;
+ goto out_put_disk;
+ }
+
disks[dr]->queue = blk_init_queue(do_fd_request, &floppy_lock);
if (!disks[dr]->queue) {
err = -ENOMEM;
- goto out_put_disk;
+ goto out_destroy_workq;
}

blk_queue_max_hw_sectors(disks[dr]->queue, 64);
@@ -4213,7 +4214,7 @@ static int __init floppy_init(void)
use_virtual_dma = can_use_virtual_dma & 1;
fdc_state[0].address = FDC1;
if (fdc_state[0].address == -1) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -ENODEV;
goto out_unreg_region;
}
@@ -4224,7 +4225,7 @@ static int __init floppy_init(void)
fdc = 0; /* reset fdc in case of unexpected interrupt */
err = floppy_grab_irq_and_dma();
if (err) {
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
err = -EBUSY;
goto out_unreg_region;
}
@@ -4281,13 +4282,13 @@ static int __init floppy_init(void)
user_reset_fdc(-1, FD_RESET_ALWAYS, false);
}
fdc = 0;
- del_timer_sync(&fd_timeout);
+ cancel_delayed_work(&fd_timeout);
current_drive = 0;
initialized = true;
if (have_no_fdc) {
DPRINT("no floppy controllers found\n");
err = have_no_fdc;
- goto out_flush_work;
+ goto out_release_dma;
}

for (drive = 0; drive < N_DRIVE; drive++) {
@@ -4302,7 +4303,7 @@ static int __init floppy_init(void)

err = platform_device_register(&floppy_device[drive]);
if (err)
- goto out_flush_work;
+ goto out_release_dma;

err = device_create_file(&floppy_device[drive].dev,
&dev_attr_cmos);
@@ -4320,13 +4321,14 @@ static int __init floppy_init(void)

out_unreg_platform_dev:
platform_device_unregister(&floppy_device[drive]);
-out_flush_work:
- flush_work_sync(&floppy_work);
+out_release_dma:
if (atomic_read(&usage_count))
floppy_release_irq_and_dma();
out_unreg_region:
blk_unregister_region(MKDEV(FLOPPY_MAJOR, 0), 256);
platform_driver_unregister(&floppy_driver);
+out_destroy_workq:
+ destroy_workqueue(floppy_wq);
out_unreg_blkdev:
unregister_blkdev(FLOPPY_MAJOR, "fd");
out_put_disk:
@@ -4397,7 +4399,7 @@ static int floppy_grab_irq_and_dma(void)
* We might have scheduled a free_irq(), wait it to
* drain first:
*/
- flush_work_sync(&floppy_work);
+ flush_workqueue(floppy_wq);

if (fd_request_irq()) {
DPRINT("Unable to grab IRQ%d for the floppy driver\n",
@@ -4488,9 +4490,9 @@ static void floppy_release_irq_and_dma(void)
pr_info("motor off timer %d still active\n", drive);
#endif

- if (timer_pending(&fd_timeout))
+ if (delayed_work_pending(&fd_timeout))
pr_info("floppy timer still active:%s\n", timeout_message);
- if (timer_pending(&fd_timer))
+ if (delayed_work_pending(&fd_timer))
pr_info("auxiliary floppy timer still active\n");
if (work_pending(&floppy_work))
pr_info("work still pending\n");
@@ -4560,8 +4562,9 @@ static void __exit floppy_module_exit(void)
put_disk(disks[drive]);
}

- del_timer_sync(&fd_timeout);
- del_timer_sync(&fd_timer);
+ cancel_delayed_work_sync(&fd_timeout);
+ cancel_delayed_work_sync(&fd_timer);
+ destroy_workqueue(floppy_wq);

if (atomic_read(&usage_count))
floppy_release_irq_and_dma();

--
Jiri Kosina
SUSE Labs

2012-05-17 16:02:55

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq

Hello,

On Thu, May 17, 2012 at 05:46:50PM +0200, Jiri Kosina wrote:
> Well, seeing almost 200 occurences of create_singlethread_workqueue() (and
> not talking about non-singlethread ones) doesn't really hint about this
> interface being deprecated :) But whatever. The patch below uses
> alloc_ordered_workqueue() instead. I have given it the same round of
> testing, it passed.

Yeah, I need to audit them and convert them to one of the newer
interfaces according to the actual requirements of each.
Eh... someday. ;)

Thanks.

--
tejun