Subject: Re: 1.4.1, APM, SCSI disk spin down how?
To: None <port-i386@netbsd.org>
From: Anne Bennett <anne@alcor.concordia.ca>
List: port-i386
Date: 07/25/2000 17:37:12
["current" folks, please cc me explicitly if you remove port-i386 from
  the To list.]

Yesterday I posted to port-i386:

> How do I get my SCSI drives to spin down when there's nothing going
> on?  The hardware manual for my motherboard implies that the BIOS
> power management will deal with IDE drives only.  I thought I might
> add something to the "standby" script and let APM do the work, but
> after reading a few "scsi" and "sd" and "ioctl" manpages, I'm still in
> the dark as to how to send a command to a SCSI disk to tell it to
> power down.  Pointers appreciated.

John Kohl <jtk@kolvir.arlington.ma.us> replied to me privately and
suggested that I try coding up something using one of the SCSI ioctls
and the SCSI command "STOP UNIT".

I responded to him with my thoughts, and he recommended posting my
thoughts to the list, so here they are...


I had been thinking of doing just that, despite the fact that I'm no
system programmer and have never coded an ioctl before (!).  However,
the manpages and include files were not providing sufficient info and
I was feeling pretty stuck.

I gave it another shot this morning, reading docs and include files
and kernel source for an hour or so.  I still can't find a way to
use an ioctl; the only thing I've found is kernel code that *perhaps*
I can adapt...

  scsi.4:
     There are a number of ioctl(2) calls that work on any SCSI device.
     They are defined in sys/scsiio.h and can be applied against any
     SCSI device that permits them.  [...] See the manual page for each
     device type for more information about how generic SCSI ioctl(2)
     calls may be applied to a specific device.  [...]

     SCIOCCOMMAND    Take a SCSI command and data from a user process
                     and apply them to the SCSI device.  Return all
                     status information and return data to the process.
                     The ioctl(2) call will return a successful status
                     even if the device rejected the command.  As all
                     status is returned to the user, it is up to the
                     user process to examine this information to decide
                     the success of the command.
     [...]
     SCIOCDECONFIG   Ask the device to disappear.  This may not happen
                     if the device is in use.

  sys/scsiio.h:
     [lots of constants defined, none of them look like "stop"
      or "power down"]
  sys/ioctl.h:
     [nothing useful, but includes:]
  sys/dkio.h:
     [nothing about stopping here either]

OK, try the source code; perhaps there's an example I can use:

  cd /usr/include/sys
  grep STOP *
  grep UNIT *
     [nope, unless I'm missing something]
  cd /usr/src/sys ; find . -name \*scsi\*
    ./arch/i386/compile/QUILL/scsibus.h  [just option defs for kernel]
    ./arch/i386/compile/QUILL/opt_scsi.h [just option defs for kernel]
    ./dev/scsipi/*  [lots of files, see below]
    ./sys/scsiio.h  [already checked out, above]

    In /usr/src/sys/dev/scsipi/scsipi_base.c, I found a subroutine
    "scsipi_start" (get scsipi driver to send a "start up" command)
    which might help spin the disk back up if ever I get it spun
    down (but I am guessing!).  Hmm, it uses a "struct scsipi_start_stop
    scsipi_cmd" into which it places a value calculated from SSS_START,
    whose name sounds promising:

------------------------------------
/*
 * Get scsipi driver to send a "start up" command
 */
int
scsipi_start(sc_link, type, flags)
        struct scsipi_link *sc_link;
        int type, flags;
{
        struct scsipi_start_stop scsipi_cmd;

        if (sc_link->quirks & SDEV_NOSTARTUNIT)
                return 0;

        bzero(&scsipi_cmd, sizeof(scsipi_cmd));
        scsipi_cmd.opcode = START_STOP;
        scsipi_cmd.byte2 = 0x00;
        scsipi_cmd.how = type;
        return (scsipi_command(sc_link,
            (struct scsipi_generic *) &scsipi_cmd, sizeof(scsipi_cmd),
            0, 0, 2, (type & SSS_START) ? 30000 : 10000, NULL, flags));
}
------------------------------------

    This suggests that something similar with SSS_STOP might do
    something useful, but now I'm way out of my depth and totally
    guessing.

    Hmm, here's an interesting code snippet from "sd.c", in the
    "sdattach" subroutine:

        ------------------------------------
        error = scsipi_start(sd->sc_link, SSS_START,
            SCSI_AUTOCONF | SCSI_IGNORE_ILLEGAL_REQUEST |
            SCSI_IGNORE_MEDIA_CHANGE | SCSI_SILENT);

        if (error)
                result = SDGP_RESULT_OFFLINE;
        else
                result = (*sd->sc_ops->sdo_get_parms)(sd, &sd->params,
                    SCSI_AUTOCONF);
        ------------------------------------

    Looks promising, but it would mean I have to find or make
    the "sd" structure in my program.  Yikes.  And "scsipi_start"
    logical-ands the type (SSS_START, here) with SSS_START, then
    uses hardcoded number 30000 or 10000 as the seventh argument
    to "scsipi_command"; oh -- that's just the timeout.  From
    file "scsipiconf.h":

------------------------------------
/*
 * Macro to issue a SCSI command.  Treat it like a function:
 *
 *      int scsipi_command __P((struct scsipi_link *link,
 *          struct scsipi_generic *scsipi_cmd, int cmdlen,
 *          u_char *data_addr, int datalen, int retries,
 *          int timeout, struct buf *bp, int flags));
 */
#define scsipi_command(l, c, cl, da, dl, r, t, b, f)                    \
        (*(l)->scsipi_cmd)((l), (c), (cl), (da), (dl), (r), (t), (b), (f))
------------------------------------

   I think I get it: "scsipi_cmd" is loaded up with the command type:

        struct scsipi_start_stop scsipi_cmd;
        [...]
        scsipi_cmd.opcode = START_STOP;
        scsipi_cmd.byte2 = 0x00;
        scsipi_cmd.how = type;

   and according to "scsipi_disk.h":

        #define START_STOP              0x1b
        struct scsipi_start_stop {
                u_int8_t opcode;
                u_int8_t byte2;
                u_int8_t unused[2];
                u_int8_t how;
        #define SSS_STOP                0x00
        #define SSS_START               0x01
        #define SSS_LOEJ                0x02
                u_int8_t control;
        };

    though I devoutly wish the above were commented!


Hmm, so maybe, by adapting some of the code above, I can try to send
an SSS_STOP (not knowing if that's even the right command!), as long as
I can first get my hands on the struct scsipi_link that corresponds to
the disk I want to talk to.  Of course, as a non-system programmer, I
don't even know if that data structure is one that *can* be accessed
by userland programs!

Interestingly, Manuel Bouyer <bouyer@antioche.lip6.fr> followed up to
my posting:

> There's nothing available for this yet. Hacking the sd driver to do a
> simple power management shouldn't be hard.

Hard for whom?!  :-)

I could *possibly* eventually figure out how to add a hook into the
kernel that I can call from userland, so that I can start and stop the
disks at will, but even if I get it to work on my system, I have no
kernel programming experience at all, so I don't think any such patch
from me should be incorporated into -current without some *very* hard
scrutiny.

Also, stopping the disks is nice, but if I need to read the disks to
fire up the command to start the disks, well, you see the problem...
We'd have to set it up so that the kernel APM support spins up the
disks upon "resume", I think.  And if we do that, we might as well
spin them down on standy or suspend, no?

I'm out of my depth here.  I don't suppose anyone who would find this
easy is interested in the project? :-)

Also, can someone confirm that SSS_START and SSS_STOP should indeed
cause the disks to spin up and spin down?  There's no documentation
(that I can find), even in the source itself.


Anne.
-- 
Ms. Anne Bennett, Senior Analyst, IITS, Concordia University, Montreal H3G 1M8
anne@alcor.concordia.ca                                        +1 514 848-7606