Skip to contentSkip to conversation

IDE Power Management try 1

RFC/PATCH Benjamin Herrenschmidt 4 participants 14 messages Apr 24, 2003 – Apr 25, 2003 Block layer

lore
Share & tools
lore
#0 lore raw

This patch is a first try at implementing proper IDE power management. It's not intended to be merged as-is, there is some uglyness here or there, it's here for comments and discussion.

The point is to pipe the power management requests through the request queue for proper locking. Since those requests involve several operations that have to be tied together with the queue beeing locked for further 'user' requests, they are implemented as a state machine with specific callbacks in the subdrivers.

The guts of the patch is the core changes. I did the actual implementation for ide-disk & ide-cd as a way to quickly validate it (it works), but I'm sure we want to do more than just what I implemented here. At least it's ok for PPC, but I beleive some x86 will want you to do more (Alan suggested reverting to PIO for example, to please some BIOSes, though that would suck badly for suspend-to-disk, there may be more involved regarding recovering from errors in flush too).

One thing that should probably be cleaned up is the difference between the suspend and the resume request. I didn't want to implement 2 different request bits to avoid using too much of that bit-space, and because most of the core handling is the same. So right now, I carry in the special structure attached to the request, 2 fields. An int indicating if we are doing a suspend or a resume op, and an int that is the actual state machine step.

However, for convenience, my ide-cd and ide-disk implementation implement resume just as different step number in the same routine...

So we could either get rid of the "int suspend" field completely and just define 2 different ranges for "step". Or we could keep "suspend", but then it may make sense to split the sub-driver ops in 2 (suspend/suspend_completion & resume/resume_completion).

Waiting for feedback...

Ben.

===== drivers/ide/ide-cd.c 1.43 vs edited =====

drivers/ide/ide-cd.c +46 -0
@@ -3242,6 +3242,46 @@
 
 static int ide_cdrom_attach (ide_drive_t *drive);
 
+static void ide_cdrom_complete_power_step (ide_drive_t *drive, ide_power_state_t *state, u8 stat, u8 error)
+{
+}
+
+/* Power Management state machine. 
+ *
+ * We don't do much for CDs right now
+ */
+static ide_startstop_t ide_cdrom_start_power_step (ide_drive_t *drive, ide_power_state_t *state)
+{
+	ide_task_t args;
+
+	memset(&args, 0, sizeof(ide_task_t));
+
+	if (state->step == 0) {
+		state->step = state->suspend ? 1 : 101;
+		return ide_stopped;
+	}
+	switch(state->step) {
+	case 1:
+		break;
+
+	case 101: /* Resume step 1 (restore DMA) */
+		/* Right now, all we do is call ide_dma_check for the HWIF,
+		 * we could be smarter and check for current xfer_speed
+		 * in struct drive etc...
+		 * Also, this step could be implemented as a generic helper
+		 * as most subdrivers will use it
+		 */
+		if (!drive->id || !(drive->id->capability & 1))
+			break;
+		if (HWIF(drive)->ide_dma_check == NULL)
+			break;
+		HWIF(drive)->ide_dma_check(drive);
+		break;
+	}
+	state->step = ide_power_state_completed;
+	return ide_stopped;
+}
+
 static ide_driver_t ide_cdrom_driver = {
 	.owner			= THIS_MODULE,
 	.name			= "ide-cdrom",
@@ -3258,6 +3298,12 @@
 	.capacity		= ide_cdrom_capacity,
 	.attach			= ide_cdrom_attach,
 	.drives			= LIST_HEAD_INIT(ide_cdrom_driver.drives),
+	.start_power_step	= ide_cdrom_start_power_step,
+	.complete_power_step	= ide_cdrom_complete_power_step,
+	.gen_driver		= {
+		.suspend	= generic_ide_suspend,
+		.resume		= generic_ide_resume,
+	}
 };
 
 static int idecd_open(struct inode * inode, struct file * file)

===== drivers/ide/ide-disk.c 1.38 vs edited =====

drivers/ide/ide-disk.c +59 -48

143 lines not on this page · raw message

===== drivers/ide/ide-io.c 1.8 vs edited =====

drivers/ide/ide-io.c +104 -1

135 lines not on this page · raw message

===== drivers/ide/ide-iops.c 1.15 vs edited =====

drivers/ide/ide-iops.c +27 -0
@@ -1318,5 +1318,32 @@
 	return do_reset1(drive, 0);
 }
 
+/*
+ * This function waits for the hwif to report a non-busy status
+ * see comments in probe_hwif()
+ */
+int ide_wait_not_busy(ide_hwif_t *hwif, unsigned long timeout)
+{
+	u8 stat = 0;
+	
+	while(timeout--) {
+		/* Turn this into a schedule() sleep once I'm sure
+		 * about locking issues (2.5 work ?)
+		 */
+		mdelay(1);
+		stat = hwif->INB(hwif->io_ports[IDE_STATUS_OFFSET]);
+		if ((stat & BUSY_STAT) == 0)
+			break;
+		/* Assume a value of 0xff means nothing is connected to
+		 * the interface and it doesn't implement the pull-down
+		 * resistor on D7
+		 */
+		if (stat == 0xff)
+			break;
+	}
+	return ((stat & BUSY_STAT) == 0) ? 0 : -EBUSY;
+}
+
+
 EXPORT_SYMBOL(ide_do_reset);
 

===== drivers/ide/ide-probe.c 1.40 vs edited =====

drivers/ide/ide-probe.c +3 -33
@@ -723,35 +723,6 @@
 
 //EXPORT_SYMBOL(hwif_register);
 
-/* Enable code below on all archs later, for now, I want it on PPC
- */
-#ifdef CONFIG_PPC
-/*
- * This function waits for the hwif to report a non-busy status
- * see comments in probe_hwif()
- */
-static int wait_not_busy(ide_hwif_t *hwif, unsigned long timeout)
-{
-	u8 stat = 0;
-	
-	while(timeout--) {
-		/* Turn this into a schedule() sleep once I'm sure
-		 * about locking issues (2.5 work ?)
-		 */
-		mdelay(1);
-		stat = hwif->INB(hwif->io_ports[IDE_STATUS_OFFSET]);
-		if ((stat & BUSY_STAT) == 0)
-			break;
-		/* Assume a value of 0xff means nothing is connected to
-		 * the interface and it doesn't implement the pull-down
-		 * resistor on D7
-		 */
-		if (stat == 0xff)
-			break;
-	}
-	return ((stat & BUSY_STAT) == 0) ? 0 : -EBUSY;
-}
-
 static int wait_hwif_ready(ide_hwif_t *hwif)
 {
 	int rc;
@@ -766,7 +737,7 @@
 	 * I know of at least one disk who takes 31 seconds, I use 35
 	 * here to be safe
 	 */
-	rc = wait_not_busy(hwif, 35000);
+	rc = ide_wait_not_busy(hwif, 35000);
 	if (rc)
 		return rc;
 
@@ -774,20 +745,19 @@
 	SELECT_DRIVE(&hwif->drives[0]);
 	hwif->OUTB(8, hwif->io_ports[IDE_CONTROL_OFFSET]);
 	mdelay(2);
-	rc = wait_not_busy(hwif, 10000);
+	rc = ide_wait_not_busy(hwif, 10000);
 	if (rc)
 		return rc;
 	SELECT_DRIVE(&hwif->drives[1]);
 	hwif->OUTB(8, hwif->io_ports[IDE_CONTROL_OFFSET]);
 	mdelay(2);
-	rc = wait_not_busy(hwif, 10000);
+	rc = ide_wait_not_busy(hwif, 10000);
 
 	/* Exit function with master reselected (let's be sane) */
 	SELECT_DRIVE(&hwif->drives[0]);
 	
 	return rc;
 }
-#endif /* CONFIG_PPC */
 
 /*
  * This routine only knows how to look for drive units 0 and 1

===== drivers/ide/ide.c 1.58 vs edited =====

drivers/ide/ide.c +46 -0

52 lines not on this page · raw message

===== include/linux/blkdev.h 1.100 vs edited =====

include/linux/blkdev.h +2 -0
@@ -116,6 +116,7 @@
 	__REQ_DRIVE_CMD,
 	__REQ_DRIVE_TASK,
 	__REQ_DRIVE_TASKFILE,
+	__REQ_POWER_MANAGEMENT,
 	__REQ_NR_BITS,	/* stops here */
 };
 
@@ -137,6 +138,7 @@
 #define REQ_DRIVE_CMD	(1 << __REQ_DRIVE_CMD)
 #define REQ_DRIVE_TASK	(1 << __REQ_DRIVE_TASK)
 #define REQ_DRIVE_TASKFILE	(1 << __REQ_DRIVE_TASKFILE)
+#define REQ_POWER_MANAGEMENT	(1 << __REQ_POWER_MANAGEMENT)
 
 #include <linux/elevator.h>
 

===== include/linux/ide.h 1.46 vs edited =====

include/linux/ide.h +39 -0
@@ -1160,6 +1160,39 @@
 #endif
 
 /*
+ * Power Management related definitions
+ */
+typedef struct ide_power_state_s
+{
+	int	suspend;	/* 1 = suspending, 0 = resuming */
+	int	step;		/* Step in PM state machine */
+	void*	data;		/* Any additional data the subdriver wants */
+} ide_power_state_t;
+
+#define ide_power_state_completed	-1
+#define ide_power_state_first		0
+//#define ide_power_state_core_first	100
+//#define ide_power_state_core_last	199
+
+/* The step value starts at 0 (ide_power_state_first). 
+ * 
+ * For each step, the core calls the subdriver start_power_step()
+ * first. This can return
+ *   - ide_stopped : In this case, the core calls us back again unless
+ *     step have been set to ide_power_state_completed
+ *   - ide_started : In this case, the channel is left busy until an
+ *     async event (interrupt) occurs.
+ * Typically, start_power_step() will issue a taskfile request with
+ * do_rw_taskfile().
+ * 
+ * Upon reception of the interrupt, the core will call complete_power_step()
+ * with the error code if any. This routine should update the step value
+ * and return. It should not start a new request. The core will call
+ * start_power_step for the new step value, unless step have been set to
+ * ide_power_state_completed.
+ */
+
+/*
  * Subdrivers support.
  */
 #define IDE_SUBDRIVER_VERSION	1
@@ -1188,6 +1221,8 @@
 	int		(*attach)(ide_drive_t *);
 	void		(*ata_prebuilder)(ide_drive_t *);
 	void		(*atapi_prebuilder)(ide_drive_t *);
+	ide_startstop_t	(*start_power_step)(ide_drive_t *, ide_power_state_t *state);
+	void		(*complete_power_step)(ide_drive_t *, ide_power_state_t *state, u8 stat, u8 error);
 	struct device_driver	gen_driver;
 	struct list_head drives;
 	struct list_head drivers;
@@ -1196,6 +1231,8 @@
 #define DRIVER(drive)		((drive)->driver)
 
 extern int generic_ide_ioctl(struct block_device *, unsigned, unsigned long);
+extern int generic_ide_suspend(struct device *dev, u32 state, u32 level);
+extern int generic_ide_resume(struct device *dev, u32 level);
 
 /*
  * IDE modules.
@@ -1550,6 +1587,8 @@
 extern u8 eighty_ninty_three (ide_drive_t *);
 extern int set_transfer(ide_drive_t *, ide_task_t *);
 extern int taskfile_lib_get_identify(ide_drive_t *drive, u8 *);
+
+extern int ide_wait_not_busy(ide_hwif_t *hwif, unsigned long timeout);
 
 /*
  * ide_system_bus_speed() returns what we think is the system VESA/PCI
#1 lore raw

Hello,

Quoted 6 lines · Benjamin Herrenschmidt

The point is to pipe the power management requests through the request queue for proper locking. Since those requests involve several operations that have to be tied together with the queue beeing locked for further 'user' requests, they are implemented as a state machine with specific callbacks in the subdrivers

[cut]

Quoted 13 lines

One thing that should probably be cleaned up is the difference between the suspend and the resume request. I didn't want to implement 2 different request bits to avoid using too much of that bit-space, and because most of the core handling is the same. So right now, I carry in the special structure attached to the request, 2 fields. An int indicating if we are doing a suspend or a resume op, and an int that is the actual state machine step.

===== include/linux/blkdev.h 1.100 vs edited =====

include/linux/blkdev.h line 121

Quoted 6 lines
include/linux/blkdev.h +1 -0
@@ -116,6 +116,7 @@
 	__REQ_DRIVE_CMD,
 	__REQ_DRIVE_TASK,
 	__REQ_DRIVE_TASKFILE,
+	__REQ_POWER_MANAGEMENT,
 	__REQ_NR_BITS,	/* stops here */
 };

What about this - add __REQ_DRIVE_INTERNAL, and carry args in
rq->cmd[16] [0] = PM, [1] = SUSPEND/RESUME, [2]= STATE ? IDE can use it for power managment, error handling (do not do it from interrupt context, but queue it), may be more. This way it would really makes things a bit better with the complicated IDE locking. SCSI and probably other block devices can benefit from this internal requests too, so the bit is not wasted.

--
have fun,
alex

#2 lore raw
Quoted 35 lines · Alexander Atanasov

Hello,

Quoted 6 lines · Benjamin Herrenschmidt

The point is to pipe the power management requests through the request queue for proper locking. Since those requests involve several operations that have to be tied together with the queue beeing locked for further 'user' requests, they are implemented as a state machine with specific callbacks in the subdrivers

[cut]

Quoted 13 lines

One thing that should probably be cleaned up is the difference between the suspend and the resume request. I didn't want to implement 2 different request bits to avoid using too much of that bit-space, and because most of the core handling is the same. So right now, I carry in the special structure attached to the request, 2 fields. An int indicating if we are doing a suspend or a resume op, and an int that is the actual state machine step.

===== include/linux/blkdev.h 1.100 vs edited =====

include/linux/blkdev.h line 121

Quoted 6 lines
include/linux/blkdev.h +1 -0
@@ -116,6 +116,7 @@
 	__REQ_DRIVE_CMD,
 	__REQ_DRIVE_TASK,
 	__REQ_DRIVE_TASKFILE,
+	__REQ_POWER_MANAGEMENT,
 	__REQ_NR_BITS,	/* stops here */
 };
		What about this - add __REQ_DRIVE_INTERNAL, and carry args 
		in rq->cmd[16] [0] = PM, [1] = SUSPEND/RESUME, [2]= STATE ? IDE can use it 
Quoted 5 lines

for power managment, error handling (do not do it from interrupt context, but queue it), may be more. This way it would really makes things a bit better with the complicated IDE locking. SCSI and probably other block devices can benefit from this internal requests too, so the bit is not wasted.

There are already lots of "INTERNAL" - basically take your pick from all the ones you quote above (DRIVE_TASK, DRIVE_CMD, DRIVE_TASKFILE - it's a MESS). A power management special request makes sense to me.

Signature · 1 line
Jens Axboe
#3 lore raw
Quoted 5 lines

There are already lots of "INTERNAL" - basically take your pick from all the ones you quote above (DRIVE_TASK, DRIVE_CMD, DRIVE_TASKFILE - it's a MESS). A power management special request makes sense to me.

Ok, well, if you prefer this way it's ok with me too ;) Though for sanity, it would have made sense then to breakup the request type into a major type (BIO, INTERNAL, POWER, ...) and a minor that is major-type dependant (READ/WRITE/BARRIER for BIO, TASKFILE,ERROR,... for INTERNAL, and SUSPEND/RESUME for POWER for example).

Ben.

#4 lore raw
In reply to Jens Axboe
Quoted 42 lines · Jens Axboe
Quoted 35 lines · Alexander Atanasov

Hello,

Quoted 6 lines · Benjamin Herrenschmidt

The point is to pipe the power management requests through the request queue for proper locking. Since those requests involve several operations that have to be tied together with the queue beeing locked for further 'user' requests, they are implemented as a state machine with specific callbacks in the subdrivers

[cut]

Quoted 13 lines

One thing that should probably be cleaned up is the difference between the suspend and the resume request. I didn't want to implement 2 different request bits to avoid using too much of that bit-space, and because most of the core handling is the same. So right now, I carry in the special structure attached to the request, 2 fields. An int indicating if we are doing a suspend or a resume op, and an int that is the actual state machine step.

===== include/linux/blkdev.h 1.100 vs edited =====

include/linux/blkdev.h line 121

Quoted 6 lines
include/linux/blkdev.h +1 -0
@@ -116,6 +116,7 @@
 	__REQ_DRIVE_CMD,
 	__REQ_DRIVE_TASK,
 	__REQ_DRIVE_TASKFILE,
+	__REQ_POWER_MANAGEMENT,
 	__REQ_NR_BITS,	/* stops here */
 };
		What about this - add __REQ_DRIVE_INTERNAL, and carry args
		in rq->cmd[16] [0] = PM, [1] = SUSPEND/RESUME, [2]= STATE ? IDE can use it
Quoted 5 lines

for power managment, error handling (do not do it from interrupt context, but queue it), may be more. This way it would really makes things a bit better with the complicated IDE locking. SCSI and probably other block devices can benefit from this internal requests too, so the bit is not wasted.

Quoted 3 lines

There are already lots of "INTERNAL" - basically take your pick from all the ones you quote above (DRIVE_TASK, DRIVE_CMD, DRIVE_TASKFILE - it's a MESS). A power management special request makes sense to me.

To me too. Alex please do not add next "INTERNAL", I am just in process of removing DRIVE_CMD and DRIVE_TASK.
And I can't see how using rq->cmd[] will help with IDE locking.

--
Bartlomiej

#5 lore raw
In reply to Alexander Atanasov
Quoted 8 lines

What about this - add __REQ_DRIVE_INTERNAL, and carry args in
rq->cmd[16] [0] = PM, [1] = SUSPEND/RESUME, [2]= STATE ? IDE can use it for power managment, error handling (do not do it from interrupt context, but queue it), may be more. This way it would really makes things a bit better with the complicated IDE locking. SCSI and probably other block devices can benefit from this internal requests too, so the bit is not wasted.

I agree. IDE locking isn't _that_ complicated ;) Though currently, we do handle requests right on interrupt completion so error handling wouldn't be deferred by this trick.

Jens, what do you think ? You are the blkdev.h guy :)

Ben.

#6 lore raw
Quoted 9 lines · Benjamin Herrenschmidt
Quoted 8 lines

What about this - add __REQ_DRIVE_INTERNAL, and carry args in
rq->cmd[16] [0] = PM, [1] = SUSPEND/RESUME, [2]= STATE ? IDE can use it for power managment, error handling (do not do it from interrupt context, but queue it), may be more. This way it would really makes things a bit better with the complicated IDE locking. SCSI and probably other block devices can benefit from this internal requests too, so the bit is not wasted.

Quoted 8 lines

I agree. IDE locking isn't _that_ complicated ;) Though currently, we do

From the request side of things, IDE is very simple.

handle requests right on interrupt completion so error handling wouldn't be deferred by this trick.

Jens, what do you think ? You are the blkdev.h guy :)

If you add REQ_DRIVE_INTERNAL, and kill the other ones I mentioned, fine with me then.

	rq->flags & REQ_DRIVE_INTERNAL
		rq->cmd[0] == PM
			pm stuf
		rq->cmd[0] = taskfile
			taskfile

etc. Make sense?

Signature · 1 line
Jens Axboe
#7 lore raw
Quoted 11 lines

If you add REQ_DRIVE_INTERNAL, and kill the other ones I mentioned, fine with me then.

	rq->flags & REQ_DRIVE_INTERNAL
		rq->cmd[0] == PM
			pm stuf
		rq->cmd[0] = taskfile
			taskfile

etc. Make sense?

As I just wrote, I'd rather go the whole way then and break up flags (which is a very bad name btw) into req_type & req_subtype, though that would mean a bit of driver fixing....

Ben.

#8 lore raw
Quoted 9 lines · Benjamin Herrenschmidt
Quoted 9 lines

If you add REQ_DRIVE_INTERNAL, and kill the other ones I mentioned, fine with me then.

	rq->flags & REQ_DRIVE_INTERNAL
		rq->cmd[0] == PM
			pm stuf
		rq->cmd[0] = taskfile
			taskfile
Quoted 5 lines
Quoted 1 line

etc. Make sense?

As I just wrote, I'd rather go the whole way then and break up flags (which is a very bad name btw) into req_type & req_subtype, though that would mean a bit of driver fixing....

Also, I noticed that my patch has a nice bug in the resume path, I use ide_preempt, which doesn't wait for the request to complete, but the request & struct state are allocated on the stack... ouch...

It would be interesting to not wait for completion of the resume still here, there's no reason why resume of the disk can't be done asynchronously since we only release the request queue once completed, so I probably need to allocate the suspend request and release it from interrupt.

Also, having a separate structure pointed to by ->special only makes this more complicated, there are plenty of fields in struct request that I could indeed use for my state information (like the cmd[] stuff)

Ben.

#9 lore raw
Quoted 26 lines · Benjamin Herrenschmidt
Quoted 9 lines · Benjamin Herrenschmidt
Quoted 9 lines

If you add REQ_DRIVE_INTERNAL, and kill the other ones I mentioned, fine with me then.

	rq->flags & REQ_DRIVE_INTERNAL
		rq->cmd[0] == PM
			pm stuf
		rq->cmd[0] = taskfile
			taskfile
Quoted 5 lines
Quoted 1 line

etc. Make sense?

As I just wrote, I'd rather go the whole way then and break up flags (which is a very bad name btw) into req_type & req_subtype, though that would mean a bit of driver fixing....

Also, I noticed that my patch has a nice bug in the resume path, I use ide_preempt, which doesn't wait for the request to complete, but the request & struct state are allocated on the stack... ouch...

It would be interesting to not wait for completion of the resume still here, there's no reason why resume of the disk can't be done asynchronously since we only release the request queue once completed, so I probably need to allocate the suspend request and release it from interrupt.

Quoted 5 lines

Also, having a separate structure pointed to by ->special only makes this more complicated, there are plenty of fields in struct request that I could indeed use for my state information (like the cmd[] stuff)

Ben.

Why can't we simply change ide_do_drive_command() to take extra flag specifing wait/do not wait and use it for ide_preempt?

--
Bartlomiej

#10 lore raw
Quoted 3 lines

Why can't we simply change ide_do_drive_command() to take extra flag specifing wait/do not wait and use it for ide_preempt?

That would do it as well.

Ben.

#11 lore raw
In reply to Benjamin Herrenschmidt
Quoted 9 lines · Benjamin Herrenschmidt
Quoted 9 lines

If you add REQ_DRIVE_INTERNAL, and kill the other ones I mentioned, fine with me then.

	rq->flags & REQ_DRIVE_INTERNAL
		rq->cmd[0] == PM
			pm stuf
		rq->cmd[0] = taskfile
			taskfile
Quoted 7 lines
Quoted 1 line

etc. Make sense?

As I just wrote, I'd rather go the whole way then and break up flags (which is a very bad name btw) into req_type & req_subtype, though that would mean a bit of driver fixing....

Ben.

req_type & req_subtype makes sense,
but it is future since driver work is needed

--
Bartlomiej

#12 lore raw
Quoted 3 lines

req_type & req_subtype makes sense,
but it is future since driver work is needed

What ? You don't want to break all drivers again ? no fun :)

Ben.

#13 lore raw
In reply to Bartlomiej Zolnierkiewicz
Quoted 14 lines · Bartlomiej Zolnierkiewicz
Quoted 11 lines · Benjamin Herrenschmidt
Quoted 10 lines

If you add REQ_DRIVE_INTERNAL, and kill the other ones I mentioned, fine with me then.

	rq->flags & REQ_DRIVE_INTERNAL
		rq->cmd[0] == PM
			pm stuf
		rq->cmd[0] = taskfile
			taskfile

etc. Make sense?

Quoted 8 lines
Quoted 5 lines

As I just wrote, I'd rather go the whole way then and break up flags (which is a very bad name btw) into req_type & req_subtype, though that would mean a bit of driver fixing....

Ben.

req_type & req_subtype makes sense,
but it is future since driver work is needed

100% agree. It's way too late to change that now. Besides, there's still plenty of space in there, especially if the IDE bits are collapsed into one.

Signature · 1 line
Jens Axboe