/*
 *  LRBug.c
 *
 *  Try to work around what seems to be a hardware bug of CyberstormPPC
 *  604e/233 cards.
 *
 *  Written by Emmanuel Lesueur lesueur@club-internet.fr
 *
 *  This code is in the public domain. You can do whatever you want with it.
 *
 *
 *  CLI Usage:
 *      LRBug NOREQ/S,QUIET/S,NOWAIT/S
 *
 *  NOREQ: don't ask before attempting to restart a task.
 *  QUIET: don't write messages with kprinf() when restarting a task.
 *  NOWAIT: don't install a handler, just restart crashed tasks and quit.
 *
 *  WB Usage:
 *      Tooltypes NOREQ, QUIET, and NOWAIT, with the same meaning.
 *
 *  To remove the handler, send a CTRL-C.
 *
 *
 *  Object:
 *      Install an exception handler to catch exceptions caused by a
 *  corrupted LR (bit 0 or 2 incorrectly set). When one is caught,
 *  clear the bad bit and restart the task.
 *
 *  Issue:
 *      Unfortunately, I don't know of any way to fall back to the default
 *  exception handler for exceptions that are not of the "bad LR" type. So
 *  in that case, I just display a small message via kprintf() and let the
 *  user run PPCShowTrap to get the full register/MMU/stack dump.
 *
 *
 *  To compile with SAS/C 6.58:
 *      sc resetopts cpu=68040 params=registers nostackcheck optimize
 *         optimizesize link stripdebug nostartup lib lib:debug.lib LRBug.c
 *
 *  To compile with gcc:
 *      gcc -o LRBug -O2 -m68040 -nostdlib LRBug.c -ldebug
 *
 *  To compile with vbcc:
 *      vc -o LRBug -O2 -cpu=68040 -nostdlib LRBug.c -lamiga -lppc -ldebug
 *
 *  Others:
 *      I don't know, but: SHOULD BE LINKED WITH NO STARTUP !
 */

#include <stdio.h>
#include <string.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <intuition/intuition.h>
#include <workbench/startup.h>
#include <powerup/ppclib/ppc.h>
#include <powerup/ppclib/tasks.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/icon.h>
#include <powerup/proto/ppc.h>

#ifdef __SASC
#   define SAVEDS       __saveds
#   define ASM          __asm
#   define REG(x,y)     register __##x y
#elif defined(__GNUC__)
#   define SAVEDS
#   define ASM
#   define REG(x,y)     y __asm__(#x)
#elif defined(_DCC)
#   define SAVEDS       __geta4
#   define ASM
#   define REG(x,y)     __##x y
#elif defined(__STORM__)
#   define SAVEDS       __saveds
#   define ASM
#   define REG(x,y)     register __##x y
#elif defined(__VBCC__)
#   define SAVEDS
#   define ASM
#   define REG(x,y)     __reg(#x) y
#else
#   error   add #defines for your compiler...
#endif

typedef BOOL ASM hook_func(REG(a0,struct Hook* h),
                           REG(a2,void* t),
                           REG(a1,struct ExceptionMsg* em));

static void* maintask;
static struct Hook* oldhook;
static BOOL noreq,quiet;

struct Library* PPCLibBase;
struct ExecBase *SysBase;
struct IntuitionBase *IntuitionBase;
struct DosLibrary *DOSBase;
struct Library *IconBase;

void kprintf(const char*,...);

void install_handler(void);
void scan_tasks_list(void);

/*
 *  Program entry point. No startup code needed.
 */
int SAVEDS start(void) {
    struct Process *proc;
    struct WBStartup *wbmsg=NULL;
    int ret=RETURN_FAIL;

    SysBase=*(struct ExecBase**)4;
    proc=(struct Process*)FindTask(NULL);
    if(!proc->pr_CLI) {
        WaitPort(&proc->pr_MsgPort);
        wbmsg=(struct WBStartup*)GetMsg(&proc->pr_MsgPort);
    }

    if((DOSBase=(struct DosLibrary*)OpenLibrary("dos.library",36)) &&
       (IntuitionBase=(struct IntuitionBase*)OpenLibrary("intuition.library",39))) {
        if(PPCLibBase=OpenLibrary("ppc.library",46)) {
            maintask=FindTask(NULL);

            if(proc->pr_CLI) {
                struct RDArgs *rda;
                LONG args[3]={0,0,0};

                if(rda=ReadArgs("NOREQ/S,QUIET/S,NOWAIT/S",args,NULL)) {
                    ret=RETURN_OK;
                    noreq=args[0];
                    quiet=args[1];
                    if(args[2])
                        scan_tasks_list();
                    else
                        install_handler();
                    FreeArgs(rda);
                } else
                    PrintFault(IoErr(),"Error: ");
            } else {
                BPTR olddir=CurrentDir(wbmsg->sm_ArgList[0].wa_Lock);
                struct DiskObject *dob;
                if(IconBase=OpenLibrary("icon.library",36)) {
                    if(dob=GetDiskObjectNew(wbmsg->sm_ArgList[0].wa_Name)) {
                        ret=RETURN_OK;
                        noreq=dob->do_ToolTypes &&
                              FindToolType((UBYTE **)dob->do_ToolTypes,
                                           (UBYTE *)"NOREQ");
                        quiet=dob->do_ToolTypes &&
                              FindToolType((UBYTE **)dob->do_ToolTypes,
                                           (UBYTE *)"QUIET");
                        if(dob->do_ToolTypes &&
                           FindToolType((UBYTE **)dob->do_ToolTypes,
                                        (UBYTE *)"NOWAIT"))
                            scan_tasks_list();
                        else
                            install_handler();
                        FreeDiskObject(dob);
                    }
                    CloseLibrary(IconBase);
                }
                CurrentDir(olddir);
            }

            CloseLibrary((struct Library*)PPCLibBase);
        } else
            Printf("Can't open ppc.library V46\n");
    }
    CloseLibrary((struct Library*)IntuitionBase);
    CloseLibrary((struct Library*)DOSBase);

    if(wbmsg) {
        Forbid();
        ReplyMsg(&wbmsg->sm_Message);
    }
    return ret;
}

/*
 *  Exception hook:
 *    - If the exception looks like one caused by the LR bug (i.e.
 *      PC==LR and bit 0 or 2 of LR set), signal our main task.
 *    - otherwise, if there was an old exception hook, call it.
 *    - otherwise, if the exception is a real one (not a kernel message),
 *      kprintf a message to signal it to the user. I'd like to
 *      fall back to the default handling (showing regs, stack, ...),
 *      but I don't think it's possible. One can always use
 *      PPCShowTrap to display those informations, anyway.
 */
BOOL SAVEDS ASM exception_func(REG(a0,struct Hook* h),
                               REG(a2,void* task),
                               REG(a1,struct ExceptionMsg* em)) {
    if(em->Type==EXCEPTION_DATAACCESS &&
       em->SRR0==em->LR &&
       ((em->LR&0xf0000000)==0x20000000 ||
        (em->LR&0xf0000000)==0x80000000)) {
        /*
         * This hook is called with task==NULL, so we don't know
         * which task to restart. To work around that problem,
         * the main task will scan the tasks list and retrieve
         * it by checking LR's. No need to save the exception message.
         */
        Signal(maintask,SIGBREAKF_CTRL_F);
        return TRUE;
    } else if(oldhook) {
        return ((hook_func*)oldhook->h_Entry)(oldhook,task,em);
    } else {
        if(!(em->Type&EXCEPTION_MSG))
            kprintf("Exception %lx. Use PPCShowTrap for details.\n",em->Type);
        return FALSE;
    }
}

static struct Hook hook={{NULL,NULL},(ULONG (*)())exception_func};

/*
 *  Restart a task.
 */
void restart(void* task,ULONG lr) {
    PPCSetTaskAttrsTags(task,
                        PPCTASKINFOTAG_PC,&lr,
                        PPCTASKINFOTAG_LR,&lr,
                        TAG_END);
    PPCStartTaskTags(task,PPCTASKSTARTTAG_RUN,TRUE,TAG_END);
}

/*
 *  Task list scanning hook. Look for tasks that have bad PC/LR,
 *  and restart them.
 */
void SAVEDS ASM scantasks_func(REG(a2,void* task)) {
    ULONG pc,lr,state;
    state=PPCGetTaskAttrsTags(task,PPCTASKINFOTAG_STATE,NULL,TAG_END);
    PPCGetTaskAttrsTags(task,PPCTASKINFOTAG_PC,&pc,TAG_END);
    PPCGetTaskAttrsTags(task,PPCTASKINFOTAG_LR,&lr,TAG_END);
    if(state==TS_WAIT && pc==lr &&
       ((lr&0xf0000000)==0x20000000 || (lr&0xf0000000)==0x80000000)) {
        static struct EasyStruct es={
            sizeof(struct EasyStruct),0,"LRBug Exception Handler",
            "LR bug caught in task\n\"%s\"",
            "Continue|Suspend",
        };
        const char* name=(const char*)PPCGetTaskAttrsTags(task,
                                                          PPCTASKINFOTAG_NAME,NULL,
                                                          TAG_END);
        if(!quiet)
            kprintf("LR bug caught in task \"%s\" (%lx).\n",name,task);
        lr&=~0xf0000000;
        if(noreq)
            restart(task,lr);
        else {
            if(EasyRequest(NULL,&es,NULL,name)==1)
                restart(task,lr);
        }

    }
}

void scan_tasks_list(void) {
    static struct Hook scantasks_hook={{NULL,NULL},(ULONG (*)())scantasks_func};

    PPCGetTaskAttrsTags(NULL,
                        PPCTASKINFOTAG_HOOK,&scantasks_hook,
                        PPCTASKINFOTAG_ALLTASK,TRUE,
                        TAG_END);
}

/*
 *  Install the exception handler and wait for events.
 */

void install_handler(void) {
    oldhook=(struct Hook *)PPCGetAttrsTags(PPCINFOTAG_EXCEPTIONHOOK,NULL,TAG_END);
    PPCSetAttrsTags(PPCINFOTAG_EXCEPTIONHOOK,&hook,TAG_END);

    while(!(Wait(SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_F)&SIGBREAKF_CTRL_C))
        scan_tasks_list();

    PPCSetAttrsTags(PPCINFOTAG_EXCEPTIONHOOK,oldhook,TAG_END);
}

