Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S965024Ab2EPThz (ORCPT ); Wed, 16 May 2012 15:37:55 -0400 Received: from cantor2.suse.de ([195.135.220.15]:37877 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932760Ab2EPThw (ORCPT ); Wed, 16 May 2012 15:37:52 -0400 Date: Wed, 16 May 2012 21:37:43 +0200 (CEST) From: Jiri Kosina To: Stephen Hemminger , Linus Torvalds , Tejun Heo Cc: Andrew Morton , Jens Axboe , linux-kernel@vger.kernel.org Subject: Re: [PATCH] floppy: convert to delayed work and single-thread wq In-Reply-To: <20120516170124.GA21275@google.com> Message-ID: References: <20120516170124.GA21275@google.com> User-Agent: Alpine 2.00 (LNX 1167 2008-08-23) MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 15903 Lines: 481 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 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:[] 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: [] dump_trace+0xaf/0x110 [] show_trace_log_lvl+0x4b/0x60 [] show_trace+0x18/0x20 [] dump_stack+0x6d/0x72 [] warn_slowpath_common+0x77/0xb0 [] warn_slowpath_fmt+0x33/0x40 [] setup_DMA+0x14c/0x210 [floppy] [] setup_rw_floppy+0x105/0x190 [floppy] [] run_timer_softirq+0x168/0x2a0 [] __do_softirq+0xc2/0x1c0 [] do_softirq+0x7d/0xb0 [] 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 Signed-off-by: Jiri Kosina --- 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 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/