/*++

Copyright (c) 2001-2002  Microsoft Corporation

Module Name:

    fatal.c

Abstract:

    This module implements the ExDisplayFatalError (aka UEM) and supporting routines.

--*/

#pragma code_seg("INIT")
#pragma data_seg("INIT_RW")
#pragma bss_seg("INIT_RW")
#pragma const_seg("INIT_RD")

#include "ntos.h"
#include "ani.h"

#include <xtl.h>
#include <xboxp.h>
#include <xconfig.h>
#include <smcdef.h>

#ifndef ARCADE

#pragma pack(1)

typedef struct _IMAGE_RUN1 {
    UINT fOne : 1;
    UINT Size : 3;
    UINT Intensity : 4;
} IMAGE_RUN1;

typedef struct _IMAGE_RUN2 {
    UINT fOne : 1;
    UINT fTwo : 1;
    UINT Size : 10;
    UINT Intensity : 4;
} IMAGE_RUN2;

typedef struct _IMAGE_RUN3 {
    UINT fOne : 1;
    UINT fTwo : 1;
    UINT Size : 18;
    UINT Intensity : 4;
} IMAGE_RUN3;

typedef struct _LED_VERT_TYPE {
    float x,y,z; 
    float w;
    ULONG color; 
} LED_VERT_TYPE;


#pragma pack()

#include "image.h"

const struct TheVerts { float x,y,z,w; float u, v; } Verts[] =
{
    {  0.0f,   0.0f, 0.5f, 1.0f,   0.0f,   0.0f},
    {640.0f, 480.0f, 0.5f, 1.0f, 320.0f, 240.0f},
    {  0.0f, 480.0f, 0.5f, 1.0f,   0.0f, 240.0f},
    {640.0f,   0.0f, 0.5f, 1.0f, 320.0f,   0.0f},
    {640.0f, 480.0f, 0.5f, 1.0f, 320.0f, 240.0f},
    {  0.0f,   0.0f, 0.5f, 1.0f,   0.0f,   0.0f},
};


//
// Macros to define LED line segments and characters
//

#define LEDSEGDEF(x1,y1,x2,y2)           ((x2<<6) | (y2<<4) | (x1<<2) | y1)
#define LEDCHARDEF(s0,s1,s2,s3,s4,s5,s6) ((s6 << 6) | (s5 << 5) | (s4 << 4) | (s3 << 3) | \
                                          (s2 << 2) | (s1 << 1) | s0)
                                          

//
// Define the line segments of the LED font
//
//   --0--
//   1   2
//   --3--
//   4   5
//   --6--
//

UCHAR LEDSegmentDef[] = {
    LEDSEGDEF(0,0, 1,0),
    LEDSEGDEF(0,0, 0,1),
    LEDSEGDEF(1,0, 1,1),
    LEDSEGDEF(0,1, 1,1),
    LEDSEGDEF(0,1, 0,2),
    LEDSEGDEF(1,1, 1,2),
    LEDSEGDEF(0,2, 1,2)
};

//
// Define the LED characters based on what line segments 
// should be turned on or off
//

UCHAR LEDCharDef[] = {
    LEDCHARDEF(1,1,1,0,1,1,1),    
    LEDCHARDEF(0,0,1,0,0,1,0),
    LEDCHARDEF(1,0,1,1,1,0,1),
    LEDCHARDEF(1,0,1,1,0,1,1),
    LEDCHARDEF(0,1,1,1,0,1,0),
    LEDCHARDEF(1,1,0,1,0,1,1),
    LEDCHARDEF(1,1,0,1,1,1,1),
    LEDCHARDEF(1,0,1,0,0,1,0),
    LEDCHARDEF(1,1,1,1,1,1,1),
    LEDCHARDEF(1,1,1,1,0,1,0)
};



IDirect3DDevice8* InitD3D(void)
{
    D3DPRESENT_PARAMETERS d3dpp;
    IDirect3DDevice8 *pDev;
    IDirect3D8 *pD3D = Direct3DCreate8(D3D_SDK_VERSION);

    if (pD3D == NULL)
    {
        return NULL;
    }

    pDev = NULL;
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    d3dpp.BackBufferWidth                 = 640;
    d3dpp.BackBufferHeight                = 480;
    d3dpp.BackBufferFormat                = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferCount                 = 1;
    d3dpp.Windowed                        = FALSE;
    d3dpp.EnableAutoDepthStencil          = TRUE;
    d3dpp.AutoDepthStencilFormat          = D3DFMT_D24S8;
    d3dpp.SwapEffect                      = D3DSWAPEFFECT_DISCARD;
    d3dpp.FullScreen_RefreshRateInHz      = 60;
    d3dpp.hDeviceWindow                   = NULL;
    d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

    IDirect3D8_CreateDevice(pD3D,
            D3DADAPTER_DEFAULT,
            D3DDEVTYPE_HAL,
            NULL,
            D3DCREATE_HARDWARE_VERTEXPROCESSING,
            &d3dpp,
            &pDev);

    IDirect3D8_Release(pD3D);

    return pDev;
}

BOOL InitVB(IDirect3DDevice8 *pDev)
{
    IDirect3DVertexBuffer8 *pVB;
    void *pVerts;

    if (FAILED(IDirect3DDevice8_CreateVertexBuffer(pDev,
            sizeof(Verts),
            D3DUSAGE_WRITEONLY,
            D3DFVF_XYZRHW | D3DFVF_TEX1,
            D3DPOOL_MANAGED,
            &pVB)))
    {
        return FALSE;
    }

    IDirect3DVertexBuffer8_Lock(pVB, 0, sizeof(Verts), (BYTE **)(&pVerts), 0);
    memcpy((void*)pVerts, (void*)Verts, sizeof(Verts));
    IDirect3DVertexBuffer8_Unlock(pVB);

    IDirect3DDevice8_SetStreamSource(pDev, 0, pVB, sizeof(Verts[0]));
    IDirect3DDevice8_SetVertexShader(pDev, D3DFVF_XYZRHW | D3DFVF_TEX1);

    return TRUE;
}

COLORREF ColorFromIntensity(UINT intensity4bits)
{
    UINT BaseR = 0x00;
    UINT BaseG = 0xFF;
    UINT BaseB = 0x00;

    UINT r = (UINT)((BaseR * intensity4bits) / 15.0f + 0.5f);
    UINT g = (UINT)((BaseG * intensity4bits) / 15.0f + 0.5f);
    UINT b = (UINT)((BaseB * intensity4bits) / 15.0f + 0.5f);

    return D3DCOLOR_ARGB(0xFF, r, g, b);
}

BOOL InitTexture(IDirect3DDevice8 *pDev)
{
    IDirect3DTexture8* pTex;
    D3DLOCKED_RECT lock;
    UINT x = 0, i, j;
    DWORD dwLine;
    DWORD dwAddr;
    IMAGE_RUN1 *pRun1;
    IMAGE_RUN2 *pRun2;
    IMAGE_RUN3 *pRun3;
    UINT Size;
    UINT Intensity;

    if (FAILED(IDirect3DDevice8_CreateTexture(pDev,
            320,
            240,
            1,
            0,
            D3DFMT_LIN_X8R8G8B8,
            0,
            &pTex)))
    {
        return FALSE;
    }

    IDirect3DTexture8_LockRect(pTex, 0, &lock, NULL, 0);
    dwLine = (DWORD)lock.pBits;
    dwAddr = dwLine;
    i = 0;

    while(i < sizeof(g_Image))
    {
        pRun1 = (IMAGE_RUN1*)&g_Image[i];
        if (pRun1->fOne)
        {
            Size = pRun1->Size;
            Intensity = pRun1->Intensity;
            i += 1;
        }
        else
        {
            pRun2 = (IMAGE_RUN2*)&g_Image[i];
            if (pRun2->fTwo)
            {
                Size = pRun2->Size;
                Intensity = pRun2->Intensity;
                i += 2;
            }
            else
            {
                pRun3 = (IMAGE_RUN3*)&g_Image[i];
                Size = pRun3->Size;
                Intensity = pRun3->Intensity;
                i += 3;
            }
        }

        for (j = 0; j < Size; j++)
        {
            if (Intensity == 0)
            {
                *(DWORD*)dwAddr = 0xFF000000;
            }
            else
            {
                *(DWORD*)dwAddr = ColorFromIntensity(Intensity);
            }

            x++;

            if (x >= 320)
            {
                x = 0;
                dwLine += lock.Pitch;
                dwAddr = dwLine;
            }
            else
            {
                dwAddr += sizeof(DWORD);
            }
        }
    }

    IDirect3DTexture8_UnlockRect(pTex, 0);

    IDirect3DDevice8_SetTexture(pDev, 0, (D3DBaseTexture*)pTex);

    IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
    IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
    IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
    IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
    IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
    IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
    IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

    return TRUE;
}

#endif

VOID
ExDisplayFatalError(
    IN ULONG ErrorCode
    )
/*++

Routine Description:

    This routine displays a fatal error message (AKA UEM).

Arguments:

    ErrorCode - The error code to be displayed 

Return Value:

    None.

--*/
{
    IDirect3DDevice8 *pDev;
    ULONG Segment;
    ULONG VertexNumber;
    UCHAR SegDef;
    UCHAR CharDef;
    ULONG Digit;
    ULONG Position;
    ULONG Scratch;
    EEPROM_LAYOUT EEPROMData;
    NTSTATUS Status;
    ULONG SettingType;
    ULONG SettingLength;
    XBOX_UEM_INFO* UEMInfo;
    BOOL PowerCycle;

    RtlZeroMemory(XboxCERTKey, XBOX_KEY_LENGTH);

    AniSetLogo(NULL, 0);
    AniTerminateAnimation();

    //
    // Read the EEPROM to get the UEM info
    //

    UEMInfo = (XBOX_UEM_INFO*)&(EEPROMData.UEMInfo[0]);

    Status = ExQueryNonVolatileSetting(XC_MAX_ALL, &SettingType, &EEPROMData,
        sizeof(EEPROMData), &SettingLength);

    if (NT_SUCCESS(Status)) {

        PowerCycle = FALSE;

        //
        // If the last UEM code stored in EEPROM is zero, write the current UEM
        // code (manufacturing only), set the matching bit in the UEM history,
        // and power cycle (manufacturing only).  Otherwise, clear the last code
        // and display the error.
        //

        if (UEMInfo->LastCode == FATAL_ERROR_NONE) {

            if (XboxGameRegion & XC_GAME_REGION_MANUFACTURING) {

                UEMInfo->LastCode = (UCHAR)ErrorCode;
                PowerCycle = TRUE;
            }

            //
            // Update the history bitmap for errors 5 and above
            //
            
            if (ErrorCode >= 5) {
                UEMInfo->History |= (1 << (ErrorCode - 5));
            }

        } else {

            UEMInfo->LastCode = FATAL_ERROR_NONE;
        }

        //
        // Write the EEPROM back out
        //

        ExSaveNonVolatileSetting(XC_MAX_ALL, SettingType, &EEPROMData, SettingLength);

        //
        // If a power cycle is required, ask the SMC to perform the power cycle
        //

        if (PowerCycle) {
            Status = HalWriteSMBusByte(SMC_SLAVE_ADDRESS, SMC_COMMAND_RESET,
                SMC_RESET_ASSERT_POWERCYCLE);
        }
    }

#ifndef ARCADE
    pDev = InitD3D();

    if (pDev != NULL) {
        if (InitVB(pDev) && InitTexture(pDev)) {

            IDirect3DDevice8_BeginScene(pDev);

            //
            // Display the texture with the multilingual text
            //

            IDirect3DDevice8_DrawPrimitive(pDev, D3DPT_TRIANGLELIST, 0,
                (sizeof(Verts) / sizeof(Verts[0])) / 3);

            //
            // Setup the state to display the error code
            //

            IDirect3DDevice8_SetVertexShader(pDev, D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
            IDirect3DDevice8_SetTextureStageState(pDev, 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2);
            
            //
            // Display the two digit error code
            //

            for (Digit = 0; Digit < 2; Digit++) {
                
                Position = 80 - Digit * 25;
                CharDef = LEDCharDef[ErrorCode % 10];

                for (Segment = 0; Segment < 7; Segment++) {
                    
                    SegDef = LEDSegmentDef[Segment];
                     
                    if ((CharDef & (1 << Segment)) != 0) {
                        
                        //
                        // Draw the line segment 
                        //

                        IDirect3DDevice8_Begin(pDev, D3DPT_LINELIST);

                        for (VertexNumber = 0; VertexNumber < 2; VertexNumber++) {
                            
                            IDirect3DDevice8_SetVertexData4f(pDev, D3DVSDE_VERTEX,  
                                (float)(Position + ((SegDef >> (VertexNumber * 4 + 2)) & 0x3) * 12),
                                (float)(50 + ((SegDef >> (VertexNumber * 4)) & 0x3) * 16),
                                0.5f, 2.0f);
                        }

                        IDirect3DDevice8_End(pDev);
                    }
                }

                ErrorCode /= 10;
            }

            IDirect3DDevice8_EndScene(pDev);

            IDirect3DDevice8_Present(pDev, NULL, NULL, NULL, NULL);

            IDirect3DDevice8_PersistDisplay(pDev);
        }
    }
#endif

    //
    // Keep the system alive so that we can continue to handle the tray eject
    // interrupt.
    //

    PsTerminateSystemThread(STATUS_SUCCESS);
}
