poff: Shell/WB command to turn off SAM460/X1000

/*
 * compile poff: gcc poff.c -N -Wall -oPOff -lauto
 * (C) Copyright 2013
 * Fredrik Wikstrom original code. Updated by Javier de las Rivas
 *
 * Based on U-boot code:
 * (C) Copyright 2009-2011
 * Max Tretene, ACube Systems Srl. mtretene@acube-systems.com.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 *
 * version 0.1 (2013.05.23): added SECONDS argument
 * version 0.2 (2013.05.25): included A1-X1000 from OS4Depot poweroff by Alex Carmona
 * version 0.3 (2013.05.25): using IExpansion->GetMachineInfoTags instead of IExec->GetCPUInfoTags
 * version 0.4 (2013.05.31): added SYNC alike Reboot cmd (THX salas00 for Sync_code)
 * version 0.5 (2013.06.09): fixed the X1000 poweroff case (THX Guillaume 'bzzd10h' Boesel)
 * version 0.6 (2013.06.11): WB/Icon support; Refactored code; ASK tooltype ;-)
 * version 1.1 (2013.06.15): [internal] added catalog/locale support; Refactored code
 * version 1.2 (2013.06.18): changed ASK req; added 'LAST CHANCE' ReAction window; cleaned-up code
 * version 1.3 (2013.06.20): now checks for SAM460/X1000 (hide POWEROFF,..); removed FreeDiskObject() duplicated
 * version 1.4 (2013.06.28): do not SYNC read only devices
 */
 
#define ALL_REACTION_CLASSES
#include <reaction/reaction.h>
#include <reaction/reaction_macros.h>
 
#include <proto/intuition.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/expansion.h>
#include <proto/icon.h>
#include <proto/requester.h>
#include <proto/locale.h>
 
#include <stdio.h>
#include <stdlib.h>
 
#include "poff_rev.h"
 
#define CATCOMP_NUMBERS
#define CATCOMP_BLOCK
#define CATCOMP_CODE
#include "poff_strings.h"
 
#define CONFIG_SYS_FPGA_BASE 0xFF000000  // SAM460ex
 
const char version[] = VERSTAG;
 
struct MsgPort *timer_mp = NULL;
struct TimeRequest *timer_io = NULL;
struct Library *ExpansionBase = NULL;
struct ExpansionIFace *IExpansion = NULL;
struct Library *IconBase = NULL;
struct IconIFace *IIcon = NULL;
struct Library *LocaleBase = NULL;
struct LocaleIFace *ILocale = NULL;
 
enum{GID_GAUGE=0,
     GID_QUIT,
     GID_LAST};
enum{WID_MAIN=0,
     WID_LAST};
enum{OID_MAIN=0,
     OID_LAST};
struct Window *windows[WID_LAST];
struct Gadget *gadgets[GID_LAST];
Object *objects[OID_LAST];
 
struct RDArgs *rdargs;
CONST_STRPTR argTemplate = "WAIT/N,SYNC/S";
STATIC struct{
              LONG *WaitTime;
              LONG SyncHDDs;
             } Args;
struct LocaleInfo li;
char msgbuffer[200]; // for use in localized strings
 
static BOOL opentimer(void) {
 timer_mp = IExec->AllocSysObject(ASOT_PORT, NULL);
 timer_io = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
                                      ASOIOR_Size, sizeof(struct TimeRequest),
                                      ASOIOR_ReplyPort, timer_mp,
                                      TAG_END);
 if(timer_io != NULL)
 {
  if(IExec->OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *)timer_io, 0) == IOERR_SUCCESS)
   return(TRUE);
  IExec->FreeSysObject(ASOT_IOREQUEST, timer_io);
 }
 IExec->FreeSysObject(ASOT_PORT, timer_mp);
 
 return(FALSE);
}
 
 
BOOL openlibs(void) {
 if( (ExpansionBase = IExec->OpenLibrary("expansion.library", 53)) == NULL)
 {
  snprintf(msgbuffer, sizeof(msgbuffer), GetString(&li, MSG_FAIL_OPEN_LIB),"expansion.library",53);
  IDOS->PutErrStr(msgbuffer);
  return(FALSE);
 }
 if( (IExpansion = (struct ExpansionIFace *)IExec->GetInterface((struct Library *)ExpansionBase, "main", 1, NULL)) == NULL)
 {
  snprintf(msgbuffer, sizeof(msgbuffer), GetString(&li, MSG_FAIL_OPEN_IFACE),"ExpansionIFace");
  IDOS->PutErrStr(msgbuffer);
  return(FALSE);
 }
 if( (IconBase = IExec->OpenLibrary("icon.library", 53)) == NULL)
 {
  snprintf(msgbuffer, sizeof(msgbuffer), GetString(&li, MSG_FAIL_OPEN_LIB),"icon.library",53);
  IDOS->PutErrStr(msgbuffer);
  return(FALSE);
 }
 if( (IIcon = (struct IconIFace *)IExec->GetInterface(IconBase, "main", 1, NULL)) == NULL)
 {
  snprintf(msgbuffer, sizeof(msgbuffer), GetString(&li, MSG_FAIL_OPEN_IFACE),"IconIFace");
  IDOS->PutErrStr(msgbuffer);
  return(FALSE);
 }
 if(!ButtonBase || !FuelGaugeBase || !WindowBase || !LayoutBase)
  {
  snprintf(msgbuffer, sizeof(msgbuffer), GetString(&li, MSG_FAIL_OPEN_IFACE),"ReAction");
  IDOS->PutErrStr(msgbuffer);
  return(FALSE);
 }
 
 return(TRUE);
}
 
 
void closelibs(void) {
 if(ILocale)
  {
   ILocale->CloseCatalog(li.li_Catalog);
   IExec->DropInterface( (struct Interface *)ILocale );
  }
 if(LocaleBase) IExec->CloseLibrary( (struct Library *)LocaleBase );
 if(IExpansion) IExec->DropInterface( (struct Interface *)IExpansion );
 if(ExpansionBase) IExec->CloseLibrary( (struct Library *)ExpansionBase );
 if(IIcon) IExec->DropInterface( (struct Interface *)IIcon );
 if(IconBase) IExec->CloseLibrary( (struct Library *)IconBase );
}
 
 
static void wait_ms(uint32 ms) {
 timer_io->Request.io_Command = TR_ADDREQUEST;
 timer_io->Time.Seconds = ms / 1000;
 timer_io->Time.Microseconds = (ms % 1000) * 1000;
 IExec->DoIO( (struct IORequest *)timer_io );
}
 
/*
static void Sync(void) {
 const uint32 flags = LDF_READ|LDF_DEVICES;
 int32 result;
 struct DeviceNode *dn;
 struct InfoData id;
 
 dn = (struct DeviceNode *)IDOS->LockDosList(flags);
 while( (dn = (struct DeviceNode *)IDOS->NextDosEntry( (struct DosList *)dn, flags)) )
 {
  if(dn->dn_Port != NULL)
  {
   if( !ILocale->StrnCmp(NULL, (char *)BADDR(dn->dn_Name)+1, "RAM", -1, SC_ASCII) )
    continue; // Skip RAM Disk
   if( !ILocale->StrnCmp(NULL, (char *)BADDR(dn->dn_Name)+1, "TEXTCLIP", -1, SC_ASCII) )
    continue; // Skip TEXTCLIP
   if( (result = IDOS->GetDiskInfoTags(GDI_MsgPortInput, dn->dn_Port,
                         GDI_InfoData, &id,
                         TAG_END) ))
   {
    if(id.id_DiskState == ID_DISKSTATE_WRITE_PROTECTED) continue; // Skip read only devices
    // Cause filesystem to flush all pending writes.
    // Inhibit should do this as well but might be useful for old
    // filesystems that don't support Inhibit to use Flush 
    result = IDOS->FlushVolumePort(dn->dn_Port); // Requires dos.library V53.90
    result = IDOS->InhibitPort(dn->dn_Port, TRUE); // Requires dos.library V53.88
   }
  }
 }
 IDOS->UnLockDosList(flags);
}
*/
 
static void Sync(void) {
 struct Node *nd = NULL;
 struct InfoData id;
 struct DevProc *dp = NULL;
 
 struct List *list = IDOS->AllocDosObjectTags(DOS_VOLUMELIST,
                                              ADO_Type, LDF_VOLUMES,
                                              ADO_AddColon, TRUE,
                                              TAG_END);
  if(list)
  {
   for( nd = IExec->GetHead(list); nd; nd = IExec->GetSucc(nd) )
   {
    dp = IDOS->GetDeviceProc(nd->ln_Name, NULL);
//    IDOS->Printf("%s (0x%lX)\n",nd->ln_Name,dp->dvp_Port);
    if( !ILocale->StrnCmp(NULL, nd->ln_Name, "RAM Disk:", -1, SC_ASCII) ) continue; // skip RAM Disk
 
    if( (IDOS->GetDiskInfoTags(GDI_MsgPortInput, dp->dvp_Port,
                               GDI_InfoData, &id,
                               TAG_END)) )
    {
     if(id.id_DiskState == ID_DISKSTATE_WRITE_PROTECTED) continue; // skip read only devices
 
     // Cause filesystem to flush all pending writes. Inhibit should do this as well but
     // might be useful for old filesystems that don't support Inhibit to use Flush.
     IDOS->FlushVolumePort(dp->dvp_Port); // Requires dos.library V53.90
     IDOS->InhibitPort(dp->dvp_Port, TRUE); // Requires dos.library V53.88
    }
  }
  IDOS->FreeDeviceProc(dp);
  IDOS->FreeDosObject(DOS_VOLUMELIST,list);
 }
}
 
 
static inline void out_be16(void *p, uint16 v) {
 volatile uint16 *reg = p;
 __asm volatile ("sync");
 *reg = v;
 __asm volatile ("sync; isync");
}
 
 
int main(int argc, char **argv) {
 uint16 fpga_val, result=3, answer=0; // default values/options
 uint32 cpum;
 int32 sec=0;
 STRPTR TTarg = NULL;
 BOOL syncON = FALSE;
 char reqtext[200], reqbtns[100], fuelgtext[10];
 Object *requester;
 struct WBStartup *WBenchMsg = NULL;
 struct DiskObject *dobj = NULL;
 
 li.li_Catalog = NULL;
 if( (LocaleBase = IExec->OpenLibrary("locale.library", 53)) && 
     (ILocale = (struct LocaleIFace *)IExec->GetInterface(LocaleBase, "main", 1, NULL))
   )
 {
  li.li_ILocale = ILocale;
  li.li_Catalog = ILocale->OpenCatalog(NULL, "poff.catalog",
                                       OC_BuiltInLanguage, "english",
                                       OC_PreferExternal, TRUE,
                                       TAG_END);
 }
 else IDOS->PutErrStr("POFF: Failed to use catalog system. Using built-in strings.\n");
 
 if(openlibs() == FALSE)
 {
  closelibs();
  return(RETURN_FAIL);
 }
 
 if(opentimer() == FALSE)
 {
  snprintf(msgbuffer, sizeof(msgbuffer), GetString(&li, MSG_FAIL_OPEN_IFACE),"timer.device");
  IDOS->PutErrStr(msgbuffer);
  closelibs();
  return(RETURN_FAIL);
 }
 
// IExec->GetCPUInfoTags(GCIT_Model, &cpum, TAG_DONE); // get cpu model
 IExpansion->GetMachineInfoTags(GMIT_Machine, &cpum, TAG_DONE); // get machine model
 
 if(argc == 0) WBenchMsg = (struct WBStartup *)argv;
 if(WBenchMsg)
 {
//  IDOS->Printf("started from WB\n");
  // get disk object
  dobj = IIcon->GetDiskObjectNew("PROGDIR:poff");
  if(dobj)
  {
   // check for "WAIT" tooltype
   TTarg = IIcon->FindToolType(dobj->do_ToolTypes, "WAIT");
   if(TTarg) sec=atoi(TTarg);
   // check for "SYNC" tooltype
   TTarg = IIcon->FindToolType(dobj->do_ToolTypes, "SYNC");
   if(TTarg)
   {
    syncON = TRUE;
    snprintf(reqtext, sizeof(reqtext), GetString(&li, MSG_REQ_ASK01B),sec,GetString(&li, MSG_REQ_ASK02B) );
   }
   else snprintf(reqtext, sizeof(reqtext), GetString(&li, MSG_REQ_ASK01B),sec,"");
   // hide POWEROFF button if no SAM460/X1000
   if(cpum==MACHINETYPE_X1000 || cpum==MACHINETYPE_SAM460)
    snprintf(reqbtns, sizeof(reqbtns), GetString(&li, MSG_GAD_BUTTONS) );
   else snprintf(reqbtns, sizeof(reqbtns), GetString(&li, MSG_GAD_BUTTONS2) );
   // check for "ASK" tooltype
   TTarg = IIcon->FindToolType(dobj->do_ToolTypes, "ASK");
   if(TTarg)
   {
    requester = IIntuition->NewObject(NULL, "requester.class",
                             REQ_Image, REQIMAGE_WARNING,
                             REQ_TitleText, VERS" ("DATE")",
                             REQ_BodyText, reqtext,
                             REQ_GadgetText, reqbtns,
                             TAG_DONE);
    result = IIntuition->IDoMethod(requester, RM_OPENREQ, NULL, NULL, NULL);
    IIntuition->DisposeObject(requester);
    if(result == 0) // user choosed CANCEL, end properly
    {
     IIcon->FreeDiskObject(dobj);
     IExec->FreeSysObject(ASOT_IOREQUEST, timer_io);
     IExec->FreeSysObject(ASOT_PORT, timer_mp);
     closelibs();
     return(RETURN_OK);
    }
   } // end if(TTarg) "ASK"
   // put the correct word (poweroff|reboot) in the 'LAST CHANCE' requester
   if(result == 3) // poweroff choosed (only SAM460/X1000)
    snprintf(reqtext, sizeof(reqtext), GetString(&li, MSG_REQ_ABORT_TXT),GetString(&li, MSG_REQ_ABORT_POWEROFF),sec);
   else // reboot choosed
    snprintf(reqtext, sizeof(reqtext), GetString(&li, MSG_REQ_ABORT_TXT),GetString(&li, MSG_REQ_ABORT_REBOOT),sec);
 
  } // end if(dobj)
  // free disk object structure & memory
  IIcon->FreeDiskObject(dobj);
 } // end if(WBenchMsg)
 else // commandline usage
 {
  if(cpum!=MACHINETYPE_X1000 && cpum!=MACHINETYPE_SAM460) // No SAM460/X1000? End properly
  {
   IDOS->PutErrStr(GetString(&li, MSG_NOT_SUPPORTED) );
   IExec->FreeSysObject(ASOT_IOREQUEST, timer_io);
   IExec->FreeSysObject(ASOT_PORT, timer_mp);
   closelibs();
   return(RETURN_WARN);
  }
  rdargs = IDOS->ReadArgs(argTemplate, (LONG *)&Args, NULL);
  if(rdargs != NULL)
  {
   if(Args.WaitTime) sec=(*Args.WaitTime);
   if(Args.SyncHDDs) syncON = TRUE;
  }
  IDOS->FreeArgs(rdargs);
 }
 
 /* Create the window object. */
 objects[OID_MAIN] = WindowObject,
  WA_Title, VERS" ("DATE")",
  WA_Activate, TRUE,
//  WA_DepthGadget, TRUE,
  WA_DragBar, TRUE,
  WA_StayTop, TRUE,
  WINDOW_Position, WPOS_CENTERMOUSE, // WPOS_CENTERSCREEN
  WINDOW_ParentGroup, VGroupObject,
 
   LAYOUT_AddChild, gadgets[GID_GAUGE] = (struct Gadget *)FuelGaugeObject,
                     GA_ID, GID_GAUGE,
//                     GA_Text, fuelgtext,
                     FUELGAUGE_Justification, FGJ_CENTER,
                     FUELGAUGE_Min, 0,
                     FUELGAUGE_Max, sec,
                     FUELGAUGE_Level, sec,
                     FUELGAUGE_Percent, FALSE,
                     FUELGAUGE_TickSize, 0,
                     FUELGAUGE_Ticks, 0,
                    FuelGaugeEnd,
 
   LAYOUT_AddImage, LabelObject,
                     LABEL_Text, reqtext,
                     LABEL_Justification, LJ_CENTER,
                    LabelEnd,
 
   LAYOUT_AddChild, gadgets[GID_QUIT] = (struct Gadget *)ButtonObject,
                     GA_ID, GID_QUIT,
                     GA_RelVerify, TRUE,
                     GA_Text,GetString(&li, MSG_REQ_ABORT_BUTTON),
                    ButtonEnd,
 
  EndGroup,
 EndWindow;
 
// IDOS->Printf("your machine=%lu; wait=%lumsec\n",cpum,sec);
// cpum=0; // "trick" to not poweroff while testing
 if(!WBenchMsg) IDOS->Printf( GetString(&li, MSG_CLI_POWEROFF) ); // hide it if in WB/icon mode
 while(sec)
 {
  if(WBenchMsg)
  {
   /*  Object creation sucessful? */
   if(objects[OID_MAIN])
   {
    /*  Open the window. */
    if( (windows[WID_MAIN] = (struct Window *)RA_OpenWindow(objects[OID_MAIN])) )
    {
     ULONG result2, done=FALSE;
     UWORD code;
 
     /* Input Event Loop */
     sec++; // to show 0 on fuelgauge's countdown
     while(!done && sec)
     {
      sprintf(fuelgtext, "%ld",--sec);
      IIntuition->SetGadgetAttrs(gadgets[GID_GAUGE],
                   windows[WID_MAIN], NULL,
                   GA_Text, fuelgtext,
                   FUELGAUGE_Level, sec,
                   TAG_DONE);
      wait_ms(1000);
      while( (result2 = RA_HandleInput(objects[OID_MAIN], &code)) != WMHI_LASTMSG )
      {
       if( (result2 & WMHI_CLASSMASK) == WMHI_GADGETUP &&
           (result2 & WMHI_GADGETMASK) == GID_QUIT ) done = answer = TRUE;
      } // end while( (result = RA..
     } // end while(!done && sec)
    } // end if(windows[WID_MAIN]..
   } //end if(objects[OID_MAIN])
   // Disposing of the window object will also close the window if it is
   // already opened, and it will dispose of the layout object attached to it.
//   IIntuition->DisposeObject(objects[OID_MAIN]);
//   answer=TRUE; // "trick" to exit while testing
   if(answer) // user choosed ABORT 'LAST CHANCE', end properly
   {
    IIntuition->DisposeObject(objects[OID_MAIN]);
    IExec->FreeSysObject(ASOT_IOREQUEST, timer_io);
    IExec->FreeSysObject(ASOT_PORT, timer_mp);
    closelibs();
    return(RETURN_OK);
   }
   IIntuition->SetGadgetAttrs(gadgets[GID_QUIT], // 'LAST CHANCE' still visible, but disabling button
                               windows[WID_MAIN], NULL,
                               GA_Disabled, TRUE,
                               TAG_DONE);
  } //end if(WBenchMsg)
  else
  {
   IDOS->Printf(GetString(&li, MSG_CLI_POWEROFF2),sec);
   wait_ms(1000);
   sec--;
   IDOS->Printf(GetString(&li, MSG_CLI_POWEROFF2),sec); // shows 0 in countdown
  }
 } //end while(sec)
 
 if(syncON)
 {
  if(!WBenchMsg) IDOS->Printf( GetString(&li, MSG_SYNC) ); // hide it if in WB/icon mode
  Sync();
 }
 // result of first ASK req: SoftReboot=1  HardReboot=2  PowerOff=3
 if(result == 1) IExec->ColdReboot();
 if(result == 2) IExec->IceColdReboot();
 
 switch(cpum)
 {
  case MACHINETYPE_SAM460: // SAM460ex
   fpga_val = 0x000f;
   out_be16((void *)CONFIG_SYS_FPGA_BASE + 0x2E, fpga_val);
   wait_ms(300);
   fpga_val = 0x0000;
   out_be16((void *)CONFIG_SYS_FPGA_BASE + 0x2E, fpga_val);
   wait_ms(300);
   fpga_val = 0x000f;
   out_be16((void *)CONFIG_SYS_FPGA_BASE + 0x2E, fpga_val);
   wait_ms(300);
   fpga_val = 0x0010;
   out_be16((void *)CONFIG_SYS_FPGA_BASE + 0x2E, fpga_val);
   while(1);
   break;
 
  case MACHINETYPE_X1000: // A1-X1000 takes 4 secs internaly to poweroff
   *((uint8  *)(0xf5000007)) = 1;
   while(1);
//   IExec->Wait(SIGBREAKF_CTRL_C); // user can abort poweroff with CTRL+C
//   *((uint8  *)(0xf5000007)) = 0;
   break;
 
  default:
   IDOS->PutErrStr( GetString(&li, MSG_NOT_SUPPORTED) );
   break;
  }
 
 if(objects[OID_MAIN]) IIntuition->DisposeObject(objects[OID_MAIN]);
 IExec->FreeSysObject(ASOT_IOREQUEST, timer_io);
 IExec->FreeSysObject(ASOT_PORT, timer_mp);
 closelibs();
 
 return(RETURN_OK);
}