Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Devices /
Chapter 6 - Power Manager


Using the Power Manager

You can use the Power Manager to install a sleep procedure that is executed when power to internal devices is about to be shut off or after power has just been restored. Most applications or other software components that are sensitive to the power-consumption state of the computer can use sleep procedures to perform any necessary processing at those times. See "Writing a Sleep Procedure," beginning on page 6-20, and "Installing a Sleep Procedure," beginning on page 6-18, for complete details on how to write and install sleep procedures.

The Power Manager provides routines that you can use to monitor the state of the battery charge and the status of the battery charger. See "Monitoring the Battery and Battery Charger," beginning on page 6-26, for details. In all likelihood, only utility programs will need to use these routines.

If you are writing an application that is sensitive to the clock speed of the computer, you can use the Power Manager to disable the CPU idle state when necessary.

IMPORTANT
Do not disable the idle state except when executing a routine that must run at full speed. Disabling the idle state shortens the amount of time the user can operate the computer from a battery.
If you want to ensure that a portable Macintosh computer is in the operating state at a particular time in the future, you can use the SetWUTime function to set the wakeup timer. You can use the wakeup timer in conjunction with the Time Manager, for example, when you want to use the computer to perform tasks that must be done at a specific time, like printing a large file in the middle of the night.

If you are writing a device driver for a portable Macintosh computer, you might need to use the Power Manager to control power to the subsystem that your driver controls. See "Switching Serial Power On and Off," on page 6-25, for a discussion of power control for the serial communications subsystem. For power control for other devices, consult Apple Developer Technical Support. The Power Manager cannot control power to external peripheral devices such as hard disks and CD-ROM drives because such devices have their own power supplies.

IMPORTANT
Because the Power Manager saves the contents of all of the CPU registers, including the stack pointer, before putting the computer into the sleep state, and because the contents of RAM are preserved while the computer is in the sleep state, most applications are not adversely affected by the sleep state. Because a portable Macintosh computer does not enter the idle state when almost any sort of activity is going on (or even when the watch cursor is being displayed), few programs are adversely affected by the idle state. Therefore, it is likely that your application will not have to make calls to the Power Manager.

Determining Whether the Power Manager Is Present

You can use the Gestalt function with the gestaltPowerMgrAttr selector to determine whether the Power Manager is available on a particular computer and whether certain other devices in the computer can be put into the idle or sleep state. The Gestalt function returns in the response parameter a 32-bit value that may have some or all of the following bits set:

CONST
   gestaltPMgrExists       = 0;{Power Manager is present}
   gestaltPMgrCPUIdle      = 1;{CPU can idle}
   gestaltPMgrSCC          = 2;{can stop SCC clock}
   gestaltPMgrSound        = 3;{can shut off sound circuits}
   gestaltPMgrDispatchExists = 4;{dispatch routines are present}
If the gestaltPMgrExists bit is set, the Power Manager is present. If the gestaltPMgrCPUIdle bit is set, the CPU is capable of going into a state of low power consumption. If the gestaltPMgrSCC bit is set, it is possible to stop the SCC clock, thus effectively turning off the serial ports. If the gestaltPMgrSound bit is set, it is possible to turn off power to the sound circuits. If the gestaltPMgrDispatchExists bit is set, the Power Manager dispatch routines are available; see the next section for more information.

Note
For complete details on using the Gestalt function, see the chapter "Gestalt Manager" in Inside Macintosh: Operating System Utilities.

Determining Whether the Power Manager Dispatch Routines are Present

You can use the Gestalt function with the gestaltPowerMgrAttr selector to determine whether the Power Manager dispatch routines are available on a particular computer. If the gestaltPMgrDispatchExists bit is set in the response parameter, the Power Manager dispatch routines are available.

Because more routines may be added in the future, the PMSelectorCount function (described on page 6-41) returns the number of dispatch routines that are implemented. The sample code in Listing 6-1 shows how you can use the Gestalt function to determine whether the Power Manager dispatch routines are present, and then use the PMSelectorCount function to find out which routines are supported. In this case, the sample code tests for the existence of the hard disk spindown routine (selector $07).

Listing 6-1 Determining which Power Manager dispatch routines exist

long pmgrAttributes;
Boolean routinesExist;

routinesExist = false;
if (! Gestalt(gestaltPowerMgrAttr, &pmgrAttributes))
if (pmgrAttributes & (1<<gestaltPMgrDispatchExists))
if (PMSelectorCount() >= 7) /* do the first 8 routines exist? */
   routinesExist = true;
WARNING
If you call a routine that does not exist, the call to the public Power Manager trap (if the trap exists) will return an error code, which your program could misinterpret as data.

Enabling or Disabling the Idle State

You can reset the activity timer to 15 seconds, disable or enable the idle state, and read the current CPU clock speed by using Power Manager routines.

IMPORTANT
Keep in mind that it is almost always better to design your code so that it is not affected by the idle state. If you do so, the computer can conserve power whenever possible. Note also that disabling the idle state does not disable the sleep state. To prevent your program from being adversely affected by the sleep state, you need to place a sleep procedure in the sleep queue, as described in "Installing a Sleep Procedure," beginning on page 6-18.
To reset the activity timer to count down another 15 seconds before the Power Manager puts the computer into the idle state, use the IdleUpdate function. The IdleUpdate function takes no parameters and returns the value in the Ticks global variable at the time the function was called.

If you want to disable the idle state--that is, prevent the computer from entering the idle state--for more than 15 seconds, use the DisableIdle procedure. If your application cannot tolerate the idle state at all, you can call the DisableIdle procedure when your application starts up and then call the EnableIdle procedure when your application terminates.

The EnableIdle procedure cancels the last call to the DisableIdle procedure. Note that canceling the last call to the DisableIdle procedure is not always the same thing as enabling the idle state. For example, if the user has used the Portable control panel to disable the idle state, then a call to the EnableIdle procedure does not enable the idle state. Similarly, if your routine called the DisableIdle procedure more than once or if another routine has called the DisableIdle procedure, then a call to the EnableIdle procedure cancels only the last call to the DisableIdle procedure; it does not enable the idle state.

The Power Manager does not actually reenable the idle state until every call to the DisableIdle procedure has been matched by a call to the EnableIdle procedure, and then only if the user has not disabled the idle state through the Portable (or PowerBook) control panel. For this reason, you must be very careful to match each call to the DisableIdle procedure with a single call to the EnableIdle procedure. Be careful to avoid making extra calls to the EnableIdle procedure so that you do not inadvertently reenable the idle state while another application needs it to remain disabled.

Calls to the EnableIdle procedure are not cumulative; that is, after you make several calls to the EnableIdle procedure, a single call to the DisableIdle procedure still disables the idle state. Disabling the idle state always takes precedence over enabling the idle state. A call to the DisableIdle procedure disables the idle state no matter how many times the EnableIdle procedure has been called and whether or not the user has enabled the idle state through the Portable or PowerBook control panel.

The following examples should help to clarify the use of EnableIdle and DisableIdle:

To determine whether a portable Macintosh computer is currently in the idle state, read the current clock speed with the GetCPUSpeed function. If the value returned by the GetCPUSpeed function is 1, the computer is in the idle state.

Setting, Disabling, and Reading the Wakeup Timer

When a portable Macintosh computer is in the sleep state, the power management hardware updates the real-time clock and compares it to the wakeup timer once each second. When the real-time clock and the wakeup timer have the same setting, the power management circuits return the computer to the operating state. The Power Manager provides functions that you can use to set the wakeup timer, disable the wakeup timer, and read the wakeup timer's current setting.

IMPORTANT
In some portable Macintosh computers, the power management hardware does not receive this periodic "tickle." As a result, the wakeup timer cannot be used on those machines. To determine whether a particular portable Macintosh computer supports the use of the wakeup timer, call the GetWUTime function. An error is returned if the timer is not available.
Use the SetWUTime function to set the wakeup timer. You pass one parameter to the SetWUTime function, an unsigned long word specifying the number of seconds since midnight, January 1, 1904. Setting the wakeup timer automatically enables it. Listing 6-2 illustrates how to call the SetWUTime function.

Listing 6-2 Setting the wakeup timer

FUNCTION WakeMeUp (when: LongInt): OSErr;
VAR
   myTime:  LongInt;
BEGIN
   GetDateTime(myTime);                   {get the current time}
   myTime := myTime + when;               {add desired delay}
   WakeMeUp := SetWUTime(LongInt(@myTime));
END;
The when parameter passed to the WakeMeUp function defined in Listing 6-2 specifies how long from the current time the wakeup timer should go off. The WakeMeUp function determines the current time by calling GetDateTime and then passes the appropriate value to SetWUTime. Note that the parameter passed to SetWUTime is the address of the desired wakeup time, not the wakeup time itself.

To disable the wakeup timer, you can set the wakeup timer to any time earlier than the current setting of the real-time clock (that is, to some time in the past), or you can use the DisableWUTime function. To reenable the wakeup timer, you must use the SetWUTime function to set the timer to a new time in the future.

To get the current setting of the wakeup timer, use the GetWUTime function. This function returns two parameters: the time to which the wakeup timer is set (in seconds since midnight, January 1, 1904) and a flag indicating whether the wakeup timer is enabled.

If the computer is already in the operating state when the real-time clock reaches the setting in the wakeup timer, nothing happens.

Note
The power management circuits do not return the computer to the operating state while battery voltage is low, even if the wakeup timer and real-time clock settings coincide.

Installing a Sleep Procedure

If you want your program to be notified before the Power Manager puts a portable Macintosh computer into the sleep state or returns it to the operating state, you can put an entry in the sleep queue. If you do place an entry in the sleep queue, remember to remove it before your device driver or application terminates.

The sleep queue is a standard operating-system queue, as described in Inside Macintosh: Operating System Utilities. The SleepQRec data type defines a sleep queue record as follows:

TYPE SleepQRec =                    {sleep queue record}
   RECORD
      sleepQLink:    SleepQRecPtr;  {next queue element}
      sleepQType:    Integer;       {queue type = 16}
      sleepQProc:    ProcPtr;       {pointer to sleep procedure}
      sleepQFlags:   Integer;       {reserved}
   END;
To add an entry to the sleep queue, fill in the sleepQType and sleepQProc fields of a sleep queue record. The sleepQLink and sleepQFlags fields are maintained privately by the Power Manager; your application should not modify these fields, except to initialize them before it calls the SleepQInstall procedure. SleepQInstall takes one parameter, a pointer to your sleep queue record. Listing 6-3 shows how to add an entry to the sleep queue.

Listing 6-3 Adding an entry to the sleep queue

VAR
   gSleepRec:     SleepQRec;        {a sleep queue record}

PROCEDURE MyInstallSleepProcedure;
BEGIN
   {Set up the record before installing it into the sleep queue.}
   WITH gSleepRec DO
   BEGIN
      sleepQLink := NIL;            {initialize reserved field}
      sleepQType := slpQType;       {set sleep queue type}
      sleepQProc := @MySleepProc;   {set address of sleep proc}
      sleepQFlags := 0;             {initialize reserved field}
   END;
   SleepQInstall(@gSleepRec);       {install the record}
END;
To remove your routine from the sleep queue, use the SleepQRemove procedure. This procedure also takes as its one parameter a pointer to your sleep queue record.

Using Application Global Variables in Sleep Procedures

When a sleep procedure installed by an application is called, the A5 world of that application might not be valid. That is to say, the A5 register might not point to the boundary between the application's global variables and its application parameters. When this happens, any attempt by the sleep procedure to read the application's global variables or to access any other information in the application's A5 world is likely to return erroneous information.

As a result, if you use an application to install a sleep procedure and your sleep procedure accesses any information in your application's A5 world, you'll need to make sure that, at the time you access that information, the A5 register points to your application's global variables. Your sleep procedure must also restore the A5 register to its previous value before exiting. This saving and restoring of the A5 register is necessary whenever your sleep procedure uses any information in your application's A5 world, such as your application global variables or any of your application's QuickDraw global variables.

Note
The techniques described in this section are relevant only to sleep procedures installed by applications. Sleep procedures installed from other kinds of code (for example, from system extensions) do not need to worry about saving and restoring the A5 register.
It's easy enough to use the SetA5 function to read the value of the A5 register when your sleep procedure begins executing and to restore the register immediately before your procedure exits. (See Listing 6-6 on page 6-21.) It's a bit harder to pass your application's A5 value to the sleep procedure. A standard way to do this in a high-level language like Pascal is to define a new data structure that contains both a sleep queue record and room for the A5 value. For example, you can define a structure of type SleepInfoRec, as follows:

TYPE SleepInfoRec =                 {sleep information record}
   RECORD
      mySleepQRec:   SleepQRec;     {a sleep queue record}
      mySlpRefCon:   LongInt;       {address of app's A5 world}
   END;
   SleepInfoRecPtr = ^SleepInfoRec;
Then, you simply need to call the SetCurrentA5 function at a time that your application is the current application and pass the result of that function to your sleep procedure (via the mySlpRefCon field of the sleep information record). Listing 6-4 shows how to do this.

Listing 6-4 Installing a sleep procedure that uses application global variables

VAR
   gSleepInfoRec:    SleepInfoRec;  {a sleep information record}

PROCEDURE MyInstallSleepProc;
BEGIN
   {Set up the record before installing it into the sleep queue.}
   WITH gSleepInfoRec.mySleepQRec DO
   BEGIN
      sleepQLink := NIL;            {initialize reserved field}
      sleepQType := slpQType;       {set sleep queue type}
      sleepQProc := @MySleepProc;   {set address of sleep proc}
      sleepQFlags := 0;             {initialize reserved field}
   END;

   {Install app's A5 value into expanded sleep record.}
   gSleepInfoRec.mySlpRefCon := SetCurrentA5;

   SleepQInstall(@gSleepInfoRec));  {install the record}
END;
The Power Manager puts the address you pass to SleepQInstall into register A0 when your sleep procedure is called. Thus, the sleep procedure simply needs to retrieve the SleepInfoRec record and extract the appropriate value of the application's A5 world. See the next section, "Writing a Sleep Procedure," for a sample sleep procedure that does this.

Note
For more information about your application's A5 world and routines you can use to manipulate the A5 register, see the chapter "Introduction to Memory Management" in Inside Macintosh: Memory.

Writing a Sleep Procedure

After you've added an entry to the sleep queue, the Power Manager calls your sleep procedure when the Power Manager issues a sleep request, a sleep demand, a wakeup demand, or a sleep-request revocation. Whenever the Power Manager calls your routine, the A0 register contains a pointer to your sleep queue record and the D0 register contains a sleep procedure selector code indicating the reason your routine is being called. One of four selector codes will be in the D0 register:

CONST
   sleepRequest      = 1;     {sleep request}
   sleepDemand       = 2;     {sleep demand}
   sleepWakeUp       = 3;     {wakeup demand}
   sleepRevoke       = 4;     {sleep-request revocation}
When your routine receives a sleep request, it must either allow or deny the request and place its response in the D0 register. To allow the sleep request, clear the D0 register to 0 before returning control to the Power Manager. To deny the sleep request, return a nonzero value in the D0 register. (Note that you cannot deny a sleep demand.)
Listing 6-5 defines two assembly-language glue routines that you can use to accept or deny the request from a high-level language.

Listing 6-5 Accepting and denying a sleep request

PROCEDURE MyAllowSleepRequest;
INLINE
   $7000;      {MOVEQ #0, D0}

PROCEDURE MyDenySleepRequest;
INLINE
   $7001;      {MOVEQ #1, D0}
If your routine or any other routine in the sleep queue denies the sleep request, the Power Manager sends a sleep-request revocation to each routine that it has already called with a sleep request. If none of the routines denies the sleep request, the Power Manager sends a sleep demand to each routine in the sleep queue. Because your routine will be called a second time in any case, it is not necessary to prepare for sleep in response to a sleep request; your routine need only allow or deny the sleep request by returning a result in the D0 register. Listing 6-6 shows a sample sleep procedure.

Listing 6-6 A sleep procedure

PROCEDURE MySleepProc;
VAR
   mySleepInfoPtr:   SleepInfoRecPtr;
   mySleepCommand:   LongInt;
   myOldA5:          LongInt;          {A5 upon entry to procedure}
   myCurA5:          LongInt;


BEGIN
   mySleepInfoPtr := MyGetSleepInfoPtr; {get the address of the sleep record}
   mySleepCommand := MyGetSleepCommand; {get the task we are to perform}

   {Set A5 register to app's A5 value, and save the original A5 value.}
   myOldA5 := SetA5(mySleepInfoPtr^.mySlpRefCon);

   CASE mySleepCommand OF               {do the right thing}
      sleepRequest: 
         MySleepRequest;
      sleepDemand: 
         MySleepDemand;
      sleepWakeUp: 
         MyWakeupDemand;
      sleepRevoke: 
         MySleepRevoke;
      OTHERWISE
         ;
   END; {CASE}

   myOldA5 := SetA5(myOldA5);           {restore original A5}
END;
The MySleepProc sleep procedure defined in Listing 6-6 retrieves the address of the sleep queue record contained in register A0 and the selector code contained in register D0. Then it calls the appropriate application-defined routine to handle the selector code. MySleepProc uses two assembly-language glue routines, defined in Listing 6-7, to get those values from the appropriate registers.

Listing 6-7 Retrieving the sleep queue record and the selector code

{Retrieve the address of our sleep info record from A0.}
FUNCTION MyGetSleepInfoPtr: SleepInfoRecPtr;
INLINE
   $2E88;      {MOVE.L A0, (A7)}

{Retrieve the command code for the sleep procedure from D0.}
FUNCTION MyGetSleepCommand: LongInt;
INLINE
   $2E80;      {MOVE.L D0, (A7)}
When your sleep procedure receives a sleep demand, it must prepare for the sleep state and return control to the Power Manager as quickly as possible. Because sleep demands are never sent by an interrupt handler, your sleep procedure can perform whatever tasks are necessary to prepare for sleep, including making calls to the Memory Manager. You can, for example, display an alert box to inform the user of potential problems, or you can even display a dialog box that requires the user to specify the action to be performed. However, if several applications display alert or dialog boxes, the user might become confused or alarmed. More important, if the user is not present to answer the alert box or dialog box, control is never returned to the Power Manager and the computer does not go to sleep. Listing 6-8 defines a procedure that displays a dialog box whenever a sleep demand is received.

Listing 6-8 Displaying a dialog box in response to a sleep demand

PROCEDURE MySleepDemand;
VAR
   myItem:        Integer;       {item number for ModalDialog}
   myRect:        Rect;          {rectangle for NewDialog}
   myOrigPort:    GrafPtr;       {original graphics port}
BEGIN
   myItem := 0;
   gOrigTime := TickCount;       {initialize timer}

   IF gDialog = NIL THEN         {create a dialog window}
   BEGIN
      SetRect(myRect, 50, 50, 400, 150);
      gDialog := NewDialog(NIL, myRect, '', FALSE, dBoxProc, 
                           WindowPtr(-1), FALSE, 0, gItemHandle);
   END;

   IF gDialog <> NIL THEN
   BEGIN
      GetPort(myOrigPort);       {remember current port}
      ShowWindow(gDialog);       {make dialog visible}
      SelectWindow(gDialog);
      SetPort(gDialog);
      REPEAT
         ModalDialog(@MyTimeOutFilter, myItem);
      UNTIL myItem = 1;

      HideWindow(gDialog);
      SetPort(myOrigPort);       {restore original port}
   END;
END;
To display a dialog box, you need to build the dialog box from within the sleep procedure itself to ensure that the newly created dialog box appears frontmost on the screen. You can facilitate this process by passing a handle to the dialog item list to your sleep procedure. In Listing 6-8, the global variable gItemHandle is assumed to contain a handle to the dialog item list. You can execute the following line of code early in your application's execution to set gItemHandle to the correct value:

gItemHandle := Get1Resource('DITL', kAlertDITL);
WARNING
If your sleep procedure displays an alert box or modal dialog box, the computer does not enter the sleep state until the user responds. If the computer remains in the operating state until the battery voltage drops below a preset value, the power management hardware automatically shuts off all power to the system, without preserving the state of open applications or data that has not been saved to disk. To prevent this from happening, you should automatically remove your dialog box after several minutes have elapsed.
An easy way to implement this time-out feature is to pass the ModalDialog procedure the address of a modal dialog filter function that intercepts null events until the desired amount of time has elapsed. Listing 6-9 illustrates such a filter function.

Listing 6-9 A modal dialog filter function that times out

FUNCTION MyTimeOutFilter (myDialog: DialogPtr; 
                         VAR myEvent: EventRecord; 
                         VAR myItem: Integer): Boolean;
CONST
   kTimeOutMax = 18000;    {remove dialog box after 5 minutes}
BEGIN
   MyTimeOutFilter := FALSE;

   CASE myEvent.what OF
      nullEvent: 
         BEGIN
            IF (TickCount - gOrigTime) >= kTimeOutMax THEN
            BEGIN
               myItem := 1;
               MyTimeOutFilter := TRUE;
            END;
         END;
      {handle other relevant events here}
      OTHERWISE
         ;
   END; {CASE}
END;
The global variable gOrigTime is initialized in the MySleepDemand procedure; the modal dialog filter function defined in Listing 6-9 simply waits until the appropriate number of ticks (sixtieths of a second) have elapsed before simulating a click on the OK button (assumed to be dialog item number 1).

When your routine receives a wakeup demand, it must prepare for the operating state and return control to the Power Manager as quickly as possible.

When your routine receives a sleep-request revocation, it must reverse any changes it made in response to the sleep request that preceded it and return control to the Power Manager.

Switching Serial Power On and Off

The serial I/O subsystem of a portable Macintosh computer includes the following components:

Because serial drivers always use these components in certain combinations, the Power Manager provides five serial power procedures that perform the following tasks:

If no internal modem is installed, then calling any of the power-on routines switches on power to the SCC, the serial driver chips, and the -5 volt supply.

To switch power on for port B whether or not there is an internal modem installed, use the BOn procedure. This procedure switches on power to the SCC, the serial driver chips, and the -5 volt supply.

If the internal modem is installed, then you can use the AOn procedure to switch on the modem. In this case, this procedure switches on power to the SCC, the -5 volt supply, and the modem; the internal modem does not use the serial driver chips.

If the internal modem is installed but you do not want to use it (whether or not the user has used the Portable control panel to disconnect the modem), then use the AOnIgnoreModem procedure to switch on power to the SCC, the serial driver chips, and the -5 volt supply.

Note
You can use the Power Manager's ModemStatus function to determine whether an internal modem is turned on or off. For details, see the description of ModemStatus beginning on page 6-36.

Monitoring the Battery and Battery Charger

You can use the Power Manager to monitor the status of the battery and battery charger. To do so, use the BatteryStatus function to determine the current voltage in the battery.

For most accurate results, you might want to average the voltage over some extended period of time (anywhere from 30 seconds to several minutes). The power load within a portable Macintosh computer varies dynamically, and the current draw of the various subsystems affects the voltage read at any one moment.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
3 JUL 1996