From: Jens Axboe <axboe@suse.de>

2.6 breaks really really easily if you have any traffic on a device and
issue a hdparm (or similar) command to it.  Things like set_using_dma() and
ide_set_xfer_rate() just stomp all over the drive regardless of what it's
doing right now.

I hacked something up for the SUSE kernel to fix this _almost_, it still
doesn't handle cases where you want to serialize across more than a single
channel.  Not a common case, but I think there is such hardware out there
(which?).

Clearly something needs to be done about this, it's extremely frustrating
not to be able to reliably turn on dma on a drive at all.  I'm just tossing
this one out there to solve 99% of the case, I'd like some input from you
on what you feel we should do.

Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/ide/ide-io.c  |   40 ++++++++++++++++++++++++++++++++++++++++
 25-akpm/drivers/ide/ide-lib.c |   10 +++++++---
 25-akpm/drivers/ide/ide.c     |   22 ++++++++++++++++------
 25-akpm/include/linux/ide.h   |    2 ++
 4 files changed, 65 insertions(+), 9 deletions(-)

diff -puN drivers/ide/ide.c~serialize-access-to-ide-devices drivers/ide/ide.c
--- 25/drivers/ide/ide.c~serialize-access-to-ide-devices	2005-02-22 18:16:47.000000000 -0800
+++ 25-akpm/drivers/ide/ide.c	2005-02-22 18:16:47.000000000 -0800
@@ -1220,18 +1220,28 @@ static int set_io_32bit(ide_drive_t *dri
 static int set_using_dma (ide_drive_t *drive, int arg)
 {
 #ifdef CONFIG_BLK_DEV_IDEDMA
+	int ret = -EPERM;
+
+	ide_pin_hwgroup(drive);
+
 	if (!drive->id || !(drive->id->capability & 1))
-		return -EPERM;
+		goto out;
 	if (HWIF(drive)->ide_dma_check == NULL)
-		return -EPERM;
+		goto out;
+	ret = -EIO;
 	if (arg) {
-		if (HWIF(drive)->ide_dma_check(drive)) return -EIO;
-		if (HWIF(drive)->ide_dma_on(drive)) return -EIO;
+		if (HWIF(drive)->ide_dma_check(drive))
+			goto out;
+		if (HWIF(drive)->ide_dma_on(drive))
+			goto out;
 	} else {
 		if (__ide_dma_off(drive))
-			return -EIO;
+			goto out;
 	}
-	return 0;
+	ret = 0;
+out:
+	ide_unpin_hwgroup(drive);
+	return ret;
 #else
 	return -EPERM;
 #endif
diff -puN drivers/ide/ide-io.c~serialize-access-to-ide-devices drivers/ide/ide-io.c
--- 25/drivers/ide/ide-io.c~serialize-access-to-ide-devices	2005-02-22 18:16:47.000000000 -0800
+++ 25-akpm/drivers/ide/ide-io.c	2005-02-22 18:16:47.000000000 -0800
@@ -1127,6 +1127,46 @@ void ide_stall_queue (ide_drive_t *drive
 	drive->sleeping = 1;
 }
 
+void ide_unpin_hwgroup(ide_drive_t *drive)
+{
+	ide_hwgroup_t *hwgroup = HWGROUP(drive);
+
+	if (hwgroup) {
+		spin_lock_irq(&ide_lock);
+		HWGROUP(drive)->busy = 0;
+		drive->blocked = 0;
+		do_ide_request(drive->queue);
+		spin_unlock_irq(&ide_lock);
+	}
+}
+
+void ide_pin_hwgroup(ide_drive_t *drive)
+{
+	ide_hwgroup_t *hwgroup = HWGROUP(drive);
+
+	/*
+	 * should only happen very early, so not a problem
+	 */
+	if (!hwgroup)
+		return;
+
+	spin_lock_irq(&ide_lock);
+	do {
+		if (!hwgroup->busy && !drive->blocked && !drive->doing_barrier)
+			break;
+		spin_unlock_irq(&ide_lock);
+		schedule_timeout(HZ/100);
+		spin_lock_irq(&ide_lock);
+	} while (hwgroup->busy || drive->blocked || drive->doing_barrier);
+
+	/*
+	 * we've now secured exclusive access to this hwgroup
+	 */
+	hwgroup->busy = 1;
+	drive->blocked = 1;
+	spin_unlock_irq(&ide_lock);
+}
+
 EXPORT_SYMBOL(ide_stall_queue);
 
 #define WAKEUP(drive)	((drive)->service_start + 2 * (drive)->service_time)
diff -puN drivers/ide/ide-lib.c~serialize-access-to-ide-devices drivers/ide/ide-lib.c
--- 25/drivers/ide/ide-lib.c~serialize-access-to-ide-devices	2005-02-22 18:16:47.000000000 -0800
+++ 25-akpm/drivers/ide/ide-lib.c	2005-02-22 18:16:47.000000000 -0800
@@ -434,13 +434,17 @@ void ide_toggle_bounce(ide_drive_t *driv
  
 int ide_set_xfer_rate(ide_drive_t *drive, u8 rate)
 {
+	int ret;
 #ifndef CONFIG_BLK_DEV_IDEDMA
 	rate = min(rate, (u8) XFER_PIO_4);
 #endif
-	if(HWIF(drive)->speedproc)
-		return HWIF(drive)->speedproc(drive, rate);
+	ide_pin_hwgroup(drive);
+	if (HWIF(drive)->speedproc)
+		ret = HWIF(drive)->speedproc(drive, rate);
 	else
-		return -1;
+		ret = -1;
+	ide_unpin_hwgroup(drive);
+	return ret;
 }
 
 EXPORT_SYMBOL_GPL(ide_set_xfer_rate);
diff -puN include/linux/ide.h~serialize-access-to-ide-devices include/linux/ide.h
--- 25/include/linux/ide.h~serialize-access-to-ide-devices	2005-02-22 18:16:47.000000000 -0800
+++ 25-akpm/include/linux/ide.h	2005-02-22 18:16:47.000000000 -0800
@@ -1321,6 +1321,8 @@ ide_startstop_t __ide_do_rw_disk(ide_dri
  * to the hwgroup by sleeping for timeout jiffies.
  */
 extern void ide_stall_queue(ide_drive_t *drive, unsigned long timeout);
+extern void ide_pin_hwgroup(ide_drive_t *);
+extern void ide_unpin_hwgroup(ide_drive_t *);
 
 extern int ide_spin_wait_hwgroup(ide_drive_t *);
 extern void ide_timer_expiry(unsigned long);
_