Coding to the SED API: Part 4

In Part 3 of this series, we did a code review of “ltloop”, a utility firmware application that uses the BMC to do out-of-band stress tests of PCI Express ports. In this article, we begin to examine a more general-purpose application that uses JTAG to extract register, memory and IO contents of the target. This On-Target Diagnostic (OTD), called “libtest”, is used by ASSET to test the functionality of run-control on new targets.

In the series Coding to the SED API, Part 1, Part 2 and Part 3, I did an introduction to the ScanWorks Embedded Diagnostics (SED) API, which form the foundation of OTDs. OTDs are extremely powerful in that they use run-control directly on the target to perform hardware-based functions that cannot be executed by the operating system on a running target. This adds a rich set of functionality, especially for crashed or wedged systems, or part of a system Built-In Test (BiT).

We have a demo application named “libtest” that we use on new customer targets, that verifies successful operation of the JTAG state machine and sideband signals necessary to initiate and use debug mode on x86 targets. It’s a utility function that reads and writes registers, memory and I/O, accessing architecturally visible data and showing off the power of the tool. Let’s look at the main routine here:

int main ()

{
         int numcores;
         int curcore;
         int numcpus;
         int curcpu;
         int iError = 0;
         bool pwrchk = true, scnsetup = true, savemodarch = true;
         int mHandle;
         FILE *UUTDiagsHexFile;
         char ver[200];

         UUTDiagsHexFile = NULL;

        

printf("\n\n****************************************************************\n");

         printf("*******************  ASSET Library Test Module  ****************\n");

printf("****************************************************************\n\n\n");




         ai_GetLibraryVersion(ver);

         printf("Library version = %s\n", ver);

        

         AI_pdcselector pdctarget = AI_pdc_0;




         if ((iError = ai_mOpen(pdctarget, 1, &mHandle)) != AI_SUCCESS)

         {

                 printf ("\nOpen ERROR: %s Channel %i\n" , ai_ErrorToString(iError), pdctarget);

                 return 1;

         }

                

         if ((iError = ai_mSetTargetCPUType(mHandle, AI_sandybridge)) != AI_SUCCESS)

         {

                 printf ("\nSetTargetCPUType: ERROR: %s Channel %i\n" , ai_ErrorToString(iError), pdctarget);

                 return 1;

         }

        

         ai_mConfig (mHandle, 100, UUTDiagsHexFile, 0x10000LL, pwrchk, scnsetup, savemodarch);




         printf("First IDCODE to be sure TAP driver is working\n");

         displayIDCODE(mHandle);

        

         printf("Second IDCODE to be sure TAP driver is working\n");

         displayIDCODE(mHandle);




         ai_mSetActiveCPU(mHandle, CPU_ZERO_POS);

         ai_mSetActiveCore(mHandle, CORE_ZERO_POS);

         ai_mSetActiveThread(mHandle, THREAD_ZERO_POS);




        

         printf("Entering debug mode.\n");

         iError = ai_mEnterDebugMode (mHandle);

         if (iError == AI_SUCCESS)

         {

             printf ("\nEnter Debug Mode executed ok\n");

         }

         else

         {

                 printf ("\nERROR: %s\n" , ai_ErrorToString(iError));

                 return 1;

         }




         numcores = displayTopo(mHandle, numcpus);




         memoryTest(mHandle);

        

         uint64_t fitAddr = 0x100000000-0x40;

         uint64_t fitData;

         ai_mReadMemory( mHandle, fitAddr, &fitData, AI_bw64 );

         printf( "FIT pointer: Addr: 0x%016llX, Data: 0x%016llX\n", fitAddr, fitData );




         ioTest(mHandle);




        vcutest( mHandle );




         readGPRs(mHandle);




         readMiscRegs(mHandle);




         readMSRs(mHandle);

        

         PCIScan(mHandle);

        

         for (curcore=CORE_ZERO_POS+1; curcore<numcores+CORE_ZERO_POS; curcore++)

         {

           printf("\n\nSwitching to core: %i\n", curcore);

           ai_mSetActiveCore(mHandle, curcore);

           iError = ai_mEnterDebugMode (mHandle);

           if (iError == AI_SUCCESS)

           {

             printf ("\nEnter Debug Mode executed ok\n");

           }

           else

           {

             printf ("\nERROR: %s\n" , ai_ErrorToString(iError));

             break;

           }

           readGPRs(mHandle);

         }




         readCPUIDs(mHandle, numcores);

        

         for (curcpu = CPU_ZERO_POS+1; curcpu<numcpus+CPU_ZERO_POS; curcpu++)

         {

           printf("\n\nSwitching to CPU %i\n", curcpu);

           ai_mSetActiveCPU(mHandle, curcpu);

          

           curcore = CORE_ZERO_POS;

           printf("Switching to core: %i\n", curcore);

           ai_mSetActiveCore(mHandle, curcore);

           iError = ai_mEnterDebugMode (mHandle);




           if (iError == AI_SUCCESS)

           {

               printf ("\nEnter Debug Mode executed ok\n");

               memoryTest(mHandle);

               readGPRs(mHandle);

           }

           else

           {

               printf ("\nERROR: %s\n" , ai_ErrorToString(iError));

           }

            

           for (curcore=CORE_ZERO_POS+1; curcore<numcores+CORE_ZERO_POS; curcore++)

           {

             printf("\n\nSwitching to core: %i\n", curcore);

             ai_mSetActiveCore(mHandle, curcore);

             iError = ai_mEnterDebugMode (mHandle);

             if (iError == AI_SUCCESS)

             {

               printf ("\nEnter Debug Mode executed ok\n");

             }

             else

             {

               printf ("\nERROR: %s\n" , ai_ErrorToString(iError));

               break;

             }

             readGPRs(mHandle);

           }

         readCPUIDs(mHandle, numcores); 

         }




        

         printf("\n\nSwitching to CPU %i\n", CPU_ZERO_POS);

         ai_mSetActiveCPU(mHandle, CPU_ZERO_POS);

          

         printf("Switching to core: %i\n", CORE_ZERO_POS);

         ai_mSetActiveCore(mHandle, CORE_ZERO_POS);

         ai_mSetActiveThread(mHandle, THREAD_ZERO_POS);

         iError = ai_mEnterDebugMode (mHandle);




         if (iError == AI_SUCCESS)

         {

             printf ("\nEnter Debug Mode executed ok\n");

                 perfTest(mHandle);

         }







         printf("\nDone, exiting debug mode.\n");

         iError = ai_mExitDebugMode(mHandle);         //Attempt to leave gracefully

         if (iError != AI_SUCCESS)

         {

             printf ("\nError with Exit Debug Mode: %s\n" , ai_ErrorToString(iError));

         }

         // call the close() library function

         ai_mClose(mHandle);




         printf("Session Finished!\n\n\n");

        

         return iError;
}

 

If you look carefully, you can see that the main() routine within libtest is essentially similar to that of ltloop. The preamble of both are the same, invoking the following functions sequentially:

ai_mOpen

ai_mSetTargetCPUType

ai_mConfig

ai_mGetITPScanChainTopology

ai_mSetActiveCPU

ai_mSetActiveCore

ai_mSetActiveThread

ai_mEnterDebugMode

Then, it dumps out a bunch of data, via the following functions:

memoryTest(mHandle);

ioTest

readGPRs

readMiscRegs

readMSRs

PCIScan

Keep in mind that the main goal of libtest is as a sample program and demonstration vehicle. It demonstrates how to use embedded run-control in the easiest-to-understand means possible. We didn’t write the most efficient code, as this would tend to obfuscate some of the design principles of SED, and it isn’t typically used within production systems. And you’ll see some hardcoding in there that would best be data-driven. But libtest does a good job of showing off what you can do with OTDs.

In the next article, we’ll start to dive into the main worker functions listed above, and show how they operate under the hood.