/**
 * @file daicctl.cc
 *
 * DaicCtl - Argument parsing and execution of operations.
 *
 * Copyright: 2003-2005 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: daicctl.cc,v 1.1.2.1 2006/02/28 21:35:47 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     Diehl legacy active ISDN boards control program $
 * @date        09.03.2000
 * @author      "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * </p>
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/* system includes */
#include <iostream>
#include <sstream>
#include <iomanip>
#include <new>                  // std::bad_alloc
#include <stdexcept>            // std::runtime_error
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/endian.h>
#include <errno.h>
#include <capi20.h>
#include <capi_bsd.h>

/* import includes */

#define __DAICCTL__

/* local includes */
#include "config.h"
#include "ctlrlist.h"
#include "cfgfile.h"
#include "daic_board_params.h"
#include "daic_cfgdefs.h"





/* === public definitions ================================================ */





/// The global configuration object.
static CConfig g_config;





/* === private definitions =============================================== */





/// The default D-channel protocol to use.
static std::string DAIC_DEFAULT_DPROTOCOL = "etsi";





/* === prototypes for private functions ================================== */





/**
 * Print out a list of available controllers.
 *
 * This function will print a short list of available CAPI controllers. For
 * Diehl active ISDN controllers the output contains a little bit more
 * information, so the user can easily detect which boards can be downloaded
 * and configured with this program.
 *
 * @param None.
 *
 * @retval 0                    All controllers were listed successfully.
 * @retval Else                 Error occurred getting the CAPI controller
 *                              list.
 */
static int ListControllers (void);

/**
 * Create a new board configuration file.
 *
 * @param fForceOverwrite       I:
 *                              - true
 *                                 If there is still an existing configuration
 *                                 file with the same name, overwrite it.
 *                              - false
 *                                 If there is still an existing configuration
 *                                 file with the same name, do not create a new
 *                                 configuration file.
 *
 * @retval 0                    A new configuration file was created
 *                              successfully.
 * @retval Else                 Failure, new configuration file was not or not
 *                              completely created.
 */
static int InitBoardConfig
   (bool fForceOverwrite);

/**
 * Update an existing board configuration file.
 *
 * @param fRemoveUnused         I:
 *                              - true
 *                                 Remove any non-existing board from the
 *                                 configuration file.
 *                              - false
 *                                 Leave any non-existing board in the
 *                                 configuration file untouched.
 *
 * @retval 0                    The board configuration file was updated
 *                              successfully.
 * @retval Else                 Failure, configuration file could not or not
 *                              completely be updated.
 */
static int UpdateBoardConfig
   (bool fRemoveUnused);

/**
 * Add a default configuration section for a controller to a configuration
 * database.
 *
 * @param ctlr                  I: The controller to add.
 * @param cfgFile               I/O: The configuration database to add the
 *                                 new configuration section to.
 *
 * @return Nothing.
 */
static void AddControllerToConfigDb
   (const CController &ctlr,
    CConfigFile       &cfgFile);

/**
 * Initialise a board data structure with defaults.
 *
 * @note The data structure is filled in host byte order, because it is not
 *       intended to be sent to a board.
 *
 * @param strBoardName          I: The name of the board in question.
 * @param boardData             O: The board data record to be filled.
 *
 * @return Nothing.
 */
static void SetBoardDataDefaults
   (const std::string     &strBoardName,
    DaicBoardConfigData_t &boardData);

/**
 * Add entries for a board data structure to a configuration database.
 *
 * @param strBoardName          I: The board name for its base section.
 * @param strDProtocol          I: The name of the D-channel protocol to use.
 *                                 It must match a firmware file name according
 *                                 to "te_<DProtocol>.<ext>".
 * @param boardData             I: The board data record to write.
 * @param cfgFile               I/O: The configuration database to modify.
 *
 * @return Nothing.
 */
static void AddBoardConfigToConfigDb
   (const std::string           &strBoardName,
    const std::string           &strDProtocol,
    const DaicBoardConfigData_t &boardData,
    CConfigFile                 &cfgFile);

/**
 * Perform a board download operation.
 *
 * @param None.
 *
 * @retval 0                    The download operation was successful.
 * @retval Else                 Failure.
 */
static int LoadBoards (void);

/**
 * Disable one board or all boards.
 *
 * @param None.
 *
 * @retval 0                    The disabling operation was successful.
 * @retval Else                 Failure, none or not all boards could be
 *                              disabled.
 */
static int ResetBoards (void);

/**
 * Perform a download operation for a specific board.
 *
 * @param ctlrData              I: Information about the controller to
 *                                 download.
 * @param cfgSect               I: The configuration section for the board.
 * @param cfgFile               I: The complete configuration file containing
 *                                 the section at cfgSect.
 *
 * @retval 0                    The download operation was successful.
 * @retval Else                 Failure.
 */
static int LoadOneBoard
   (const CController           &ctlrData,
    const CConfigFile::CSection &cfgSect,
    CConfigFile                 &cfgFile);

/**
 * Create a configuration data record for downloading a board.
 *
 * @param cfgSect               I: The configuration section for the board.
 * @param cfgFile               I: The complete configuration file containing
 *                                 the section at cfgSect.
 * @param strBootCodeFileName   O: On successful return the boot code file name
 *                                 to use for the board is stored here.
 * @param strFirmwareFileName   O: On successful return the firmware file name
 *                                 to use is stored here.
 * @param configData            O: On successful return the configuration data
 *                                 is stored here.
 *
 * @return Nothing.
 */
static void CreateConfigData
   (const CConfigFile::CSection &cfgSect,
    CConfigFile                 &cfgFile,
    std::string                 &strBootCodeFileName,
    std::string                 &strFirmwareFileName,
    DaicBoardConfigData_t       &configData);

/**
 * Disable a specific board.
 *
 * @param ctlrData              I: Information about the controller to disable.
 *
 * @retval 0                    The disabling operation was successful.
 * @retval Else                 Failure.
 */
static int ResetOneBoard
   (const CController &ctlrData);





/* === definition of public functions ==================================== */





/**
 * Main function.
 *
 * @param iArgc                 I: The number of command line arguments.
 * @param papszArgv             I: The command line arguments.
 *
 * @retval 0                    Program execution was successful.
 * @retval 1                    Only help requested, usage information printed.
 * @retval Else                 Error occurred during program execution.
 */

int main
   (int    iArgc,
    char **papszArgv)
{
   int iRes = 2;
   try
   {
      // first tell our config module the program name
      g_config.Init (papszArgv [0]);
      
      // pass the command line arguments to the config module for evaluation
      g_config.EvalCommandLine (iArgc, papszArgv);
      
      // check the task to execute
      switch (g_config.GetTask ())
      {
         case CConfig::TASK_USAGE:
            g_config.Usage ();
            iRes = 1;
            break;
   
         case CConfig::TASK_LIST:
            iRes = ListControllers ();
            break;

         case CConfig::TASK_INIT:
            iRes = InitBoardConfig (false);
            break;
   
         case CConfig::TASK_INIT_FORCE:
            iRes = InitBoardConfig (true);
            break;

         case CConfig::TASK_UPDATE:
            iRes = UpdateBoardConfig (false);
            break;
   
         case CConfig::TASK_UPDATE_REMOVE:
            iRes = UpdateBoardConfig (true);
            break;

         case CConfig::TASK_RESET:
            iRes = ResetBoards ();
            break;
   
         case CConfig::TASK_LOAD:
            iRes = LoadBoards ();
            break;
   
         case CConfig::TASK_UNKNOWN:
         default:
            std::cerr << g_config.GetProgName ()
                      << ": Invalid command line arguments"
                      << std::endl;
            g_config.Usage ();
            iRes = 2;
            break;
      }
   }
   catch (std::bad_alloc &)
   {
      std::cerr << g_config.GetProgName ()
                << ": Out of memory during program execution"
                << std::endl;
      iRes = 2;
   }
   catch (std::runtime_error &e)
   {
      std::cerr << g_config.GetProgName ()
                << ": " << e.what ()
                << std::endl;
      iRes = 2;
   }
   catch (std::exception &e)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error: " << e.what ()
                << std::endl;
      iRes = 2;
   }
   catch (...)
   {
      std::cerr << g_config.GetProgName ()
                << ": Fatal error during program execution (unspecified)"
                << std::endl;
      iRes = 2;
   }
   
   return (iRes);
} // main





/* === definition of private functions =================================== */





/**
 * Print out a list of available controllers.
 *
 * This function will print a short list of available CAPI controllers. For
 * Diehl active ISDN controllers the output contains a little bit more
 * information, so the user can easily detect which boards can be downloaded
 * and configured with this program.
 *
 * @param None.
 *
 * @retval 0                    All controllers were listed successfully.
 * @retval Else                 Error occurred getting the CAPI controller
 *                              list.
 */

static int ListControllers (void)
{
   CAPIProfileBuffer_t  profile;
   CAPICtlrDriverInfo_t drvInfo;
   int                  i;
   unsigned             uNumCtlr;
   unsigned             u;
   unsigned             uRes;
   
   // first get the number of installed controllers
   uRes = capi20_get_profile (0, &profile);
   if (uRes != CAPI_OK)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error getting number of installed controllers: 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
                << std::endl;
      return (2);
   }
   uNumCtlr = C_GET_WORD (profile.wCtlr);
   if (uNumCtlr == 0)
   {
      std::cout << g_config.GetProgName ()
                << ": No controllers installed"
                << std::endl;
      return (0);
   }
   
   // now query all controllers until all found
   std::cout << std::endl;
   std::cout << std::dec << uNumCtlr
             << " controllers installed:"
             << std::endl << std::endl;
   for (i = 1, u = 0; i <= 127 && u < uNumCtlr; i++)
   {
      // get information about the current controller number
      uRes = capi20_get_ctlr_driver_info ((unsigned) i, &drvInfo);
      if (uRes == CRE_CAPI_NOT_INSTALLED)
      {
         // seems to be a hole in the array of CAPI controllers...
         continue;
      }
      
      // count the controller found, even if we did not get any information
      u++;
      if (uRes != CAPI_OK)
      {
         std::cerr << g_config.GetProgName ()
                   << ": Error getting driver information for controller "
                   << std::dec << i << ": 0x"
                   << std::hex << std::setfill ('0') << std::setw (4) << uRes
                   << std::endl;
         continue;
      }
      
      // if it is an Diehl active ISDN controller, print out some more details
      if (strcasecmp (drvInfo.szDriverName,
                      g_config.GetDriverName ().c_str ()) == 0)
      {
         std::cout << '\t' << std::dec << i << ": Name \""
                   << drvInfo.szCtlrName << "\", type \""
                   << drvInfo.szCtlrTypeName << "\", driver \""
                   << drvInfo.szDriverName << "\", driver unit "
                   << C_GET_WORD (drvInfo.wDrvUnitNum)
                   << std::endl;
         if (C_GET_WORD (drvInfo.wNumPorts) > 1)
         {
            std::cout << "\t\tport " << C_GET_WORD (drvInfo.wPortIdx)
                      << " of " << C_GET_WORD (drvInfo.wNumPorts)
                      << std::endl;
         }
      }
      // for other controllers simply print out their name
      else
      {
         std::cout << '\t' << std::dec << i << ": Name \""
                   << drvInfo.szCtlrName << "\""
                   << std::endl;
      }
   }
   
   std::cout << std::endl;
   
   return (0);
} // ListControllers





/**
 * Create a new board configuration file.
 *
 * @param fForceOverwrite       I:
 *                              - true
 *                                 If there is still an existing configuration
 *                                 file with the same name, overwrite it.
 *                              - false
 *                                 If there is still an existing configuration
 *                                 file with the same name, do not create a new
 *                                 configuration file.
 *
 * @retval 0                    A new configuration file was created
 *                              successfully.
 * @retval Else                 Failure, new configuration file was not or not
 *                              completely created.
 */

static int InitBoardConfig
   (bool fForceOverwrite)
{
   if (! g_config.Quiet ())
   {
      std::cout << "Create config file for Diehl active ISDN boards..."
                << std::endl;
   }
   
   // create an empty configuration database
   CConfigFile cfgFile;
   
   // add the global section for the base directory to the database
   CConfigFile::CSectionIterator iterSect;
   iterSect = cfgFile.SectionAdd
                 ("/" DAIC_CFG_SECT_GLOBAL, DAIC_COMMENT_SECT_GLOBAL);
   iterSect->AddKeyValuePair (DAIC_CFG_KEY_BASE_DIRECTORY,
                              g_config.GetBaseDirectory (),
                              DAIC_COMMENT_KEY_BASE_DIRECTORY);

   // create list of available Diehl controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cout << "No Diehl active ISDN controllers found, no configuration file created"
                << std::endl;
      return (2);
   }
   if (! g_config.Quiet ())
   {
      std::cout << "Found " << ctlrList.size ()
                << " Diehl active ISDN controllers, creating default configuration..."
                << std::endl;
   }
   
   // for every controller found create a configuration entry
   int                             iRes = 0;
   CControllerList::const_iterator iter;
   for (iter = ctlrList.begin (); iter != ctlrList.end (); ++iter)
   {
      // leave out all controllers that are not the primary port of their board
      if (iter->GetPortIndex () > 0)
      {
         continue;
      }
      
      try
      {
         if (! g_config.Quiet ())
         {
            std::cout << "Add board \""
                      << iter->GetBoardName ()
                      << "\" to configuration database"
                      << std::endl;
         }
         AddControllerToConfigDb (*iter, cfgFile);
      }
      catch (std::bad_alloc &)
      {
         throw;
      }
      catch (std::exception &e)
      {
         std::cerr << g_config.GetProgName ()
                   << ": Error creating configuration record for board \""
                   << iter->GetBoardName ()
                   << "\": " << e.what ()
                   << std::endl;
         iRes = 2;
      }
   }
   
   // now finally save the configuration database to the file
   if (cfgFile.GetNumSections () > 0)
   {
      cfgFile.SaveToFile (g_config.GetConfigFileName (),
                          fForceOverwrite);
   
      if (! g_config.Quiet ())
      {
         std::cout << "Configuration database written to file \""
                   << cfgFile.GetFileName ()
                   << "\""
                   << std::endl;
      }
   }
   else
   {
      std::cerr << g_config.GetProgName ()
                << ": No configuration record created, empty database will not be saved"
                << std::endl;
   }
   
   return (iRes);
} // InitBoardConfig





/**
 * Update an existing board configuration file.
 *
 * @param fRemoveUnused         I:
 *                              - true
 *                                 Remove any non-existing board from the
 *                                 configuration file.
 *                              - false
 *                                 Leave any non-existing board in the
 *                                 configuration file untouched.
 *
 * @retval 0                    The board configuration file was updated
 *                              successfully.
 * @retval Else                 Failure, configuration file could not or not
 *                              completely be updated.
 */

static int UpdateBoardConfig
   (bool fRemoveUnused)
{
   if (! g_config.Quiet ())
   {
      std::cout << "Update configuration file \""
                << g_config.GetConfigFileName ()
                << "\" for Diehl active ISDN boards..."
                << std::endl;
   }
   
   // read in the existing configuration database
   CConfigFile cfgFile (g_config.GetConfigFileName ());
   
   // Create list of available Diehl controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No Diehl active ISDN controllers found, will not perform update"
                << std::endl;
      return (2);
   }
   if (! g_config.Quiet ())
   {
      std::cout << "Found " << ctlrList.size ()
                << " Diehl active ISDN controllers, update configuration..."
                << std::endl;
   }
   
   // if requested remove any non-existing controller from config database
   // first
   if (fRemoveUnused)
   {
      CConfigFile::CSectionIterator iterSect;
      for (iterSect = cfgFile.SectionBegin (DAIC_CFG_SECT_BOARDS);
           iterSect != cfgFile.SectionEnd ();
           )
      {
         if (ctlrList.find_by_board_name
                (iterSect->GetShortName ()) == ctlrList.end ())
         {
            if (! g_config.Quiet ())
            {
               std::cout << "Remove non-existing controller \""
                         << iterSect->GetShortName ()
                         << "\" from configuration database"
                         << std::endl;
            }
            cfgFile.SectionRemove (iterSect);
            
            // as the last call does not deliver a follow-up iterator and the
            // current one is now invalid, we must start a new iteration over
            // all controller sections
            iterSect = cfgFile.SectionBegin (DAIC_CFG_SECT_BOARDS);
         }
         else
         {
            ++iterSect;
         }
      }
   }

   // for every controller found create a default configuration entry, if it is
   // still unknown
   int                             iRes = 0;
   CControllerList::const_iterator iter;
   for (iter = ctlrList.begin (); iter != ctlrList.end (); ++iter)
   {
      // leave out all controllers that are not the primary port of their board
      if (iter->GetPortIndex () > 0)
      {
         continue;
      }
      
      try
      {
         if (cfgFile.SectionFind (std::string ("/" DAIC_CFG_SECT_BOARDS "/") +
                                  iter->GetBoardName ()) ==
                cfgFile.SectionEnd ())
         {
            if (! g_config.Quiet ())
            {
               std::cout << "Add board \""
                         << iter->GetBoardName ()
                         << "\" to configuration database"
                         << std::endl;
            }
            AddControllerToConfigDb (*iter, cfgFile);
         }
      }
      catch (std::bad_alloc &)
      {
         throw;
      }
      catch (std::exception &e)
      {
         std::cerr << g_config.GetProgName ()
                   << ": Error creating configuration record for board \""
                   << iter->GetBoardName ()
                   << "\": " << e.what ()
                   << std::endl;
         iRes = 2;
      }
   }
   
   // now finally save the configuration database to the file
   if (cfgFile.IsModified ())
   {
      cfgFile.Save ();
   
      if (! g_config.Quiet ())
      {
         std::cout << "Configuration database written to file \""
                   << cfgFile.GetFileName ()
                   << "\""
                   << std::endl;
      }
   }
   else
   {
      std::cout << g_config.GetProgName ()
                << ": No changes detected, database not modified"
                << std::endl;
   }
   
   return (iRes);
} // UpdateBoardConfig





/**
 * Add a default configuration section for a controller to a configuration
 * database.
 *
 * @param ctlr                  I: The controller to add.
 * @param cfgFile               I/O: The configuration database to add the
 *                                 new configuration section to.
 *
 * @return Nothing.
 */

static void AddControllerToConfigDb
   (const CController &ctlr,
    CConfigFile       &cfgFile)
{
   // first initialise a board configuration structure with default values
   DaicBoardConfigData_t boardData;
   SetBoardDataDefaults (ctlr.GetBoardName (), boardData);
   
   // finally add sections and values to the configuration database for the
   // board
   AddBoardConfigToConfigDb
      (ctlr.GetBoardName (), DAIC_DEFAULT_DPROTOCOL, boardData, cfgFile);
   
} // AddControllerToConfigDb





/**
 * Initialise a board data structure with defaults.
 *
 * @note The data structure is filled in host byte order, because it is not
 *       intended to be sent to a board.
 *
 * @param strBoardName          I: The name of the board in question.
 * @param boardData             O: The board data record to be filled.
 *
 * @return Nothing.
 */

static void SetBoardDataDefaults
   (const std::string     &strBoardName,
    DaicBoardConfigData_t &boardData)
{
   // basic defaults are to set everything to null
   memset (&boardData, 0, sizeof (boardData));
   
   // for S2m boards a fixed TEI of 0 is used and permanent layer 2 connection
   if (strBoardName.find ("S2m") != std::string::npos)
   {
      boardData.ucTei      = 0x01;
      boardData.ucNt2      = 1;
      boardData.ucStableL2 = 0x02;
   }
   // S0 boards use by default automatic TEI assignment and no disconnect for
   // the layer 2 connection
   else
   {
      boardData.ucTei      = 0x00;
      boardData.ucNt2      = 0;
      boardData.ucStableL2 = 0x01;
   }
   
} // SetBoardDataDefaults





/**
 * Add entries for a board data structure to a configuration database.
 *
 * @param strBoardName          I: The board name for its base section.
 * @param strDProtocol          I: The name of the D-channel protocol to use.
 *                                 It must match a firmware file name according
 *                                 to "te_<DProtocol>.<ext>".
 * @param boardData             I: The board data record to write.
 * @param cfgFile               I/O: The configuration database to modify.
 *
 * @return Nothing.
 */

static void AddBoardConfigToConfigDb
   (const std::string           &strBoardName,
    const std::string           &strDProtocol,
    const DaicBoardConfigData_t &boardData,
    CConfigFile                 &cfgFile)
{
   CConfigFile::CSectionIterator iter;
   std::string                   strSect;
   
   // create the board section
   strSect = std::string ("/" DAIC_CFG_SECT_BOARDS) + "/" + strBoardName;
   iter = cfgFile.SectionAdd (strSect);
   
   // add the configuration key / value pairs
   iter->AddKeyValuePair (DAIC_CFG_KEY_DPROTOCOL,
                          strDProtocol,
                          DAIC_COMMENT_KEY_DPROTOCOL);
   iter->AddKeyValuePair (DAIC_CFG_KEY_NT2,
                          (unsigned) (boardData.ucNt2),
                          DAIC_COMMENT_KEY_NT2);
   iter->AddKeyValuePair (DAIC_CFG_KEY_TEI,
                          boardData.ucTei >> 1,
                          DAIC_COMMENT_KEY_TEI);
   iter->AddKeyValuePair (DAIC_CFG_KEY_PERMANENT,
                          (unsigned) (boardData.ucPermanent),
                          DAIC_COMMENT_KEY_PERMANENT);
   iter->AddKeyValuePair (DAIC_CFG_KEY_LAYER2,
                          (unsigned) (boardData.ucStableL2),
                          DAIC_COMMENT_KEY_LAYER2);
   
   if (strBoardName.find ("S2m") != std::string::npos)
   {
      iter->AddKeyValuePair (DAIC_CFG_KEY_LOW_CHANNEL,
                             (unsigned) (boardData.ucLowChannel),
                             DAIC_COMMENT_KEY_LOW_CHANNEL);
      iter->AddKeyValuePair (DAIC_CFG_KEY_CRC4,
                             (unsigned) (boardData.ucCrc4),
                             DAIC_COMMENT_KEY_CRC4);
   }
   
} // AddBoardConfigToConfigDb





/**
 * Perform a board download operation.
 *
 * @param None.
 *
 * @retval 0                    The download operation was successful.
 * @retval Else                 Failure.
 */

static int LoadBoards (void)
{
   // read in the existing configuration database
   CConfigFile cfgFile (g_config.GetConfigFileName ());
   
   // Create list of available Diehl controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No Diehl active ISDN controllers found, unable to perform download"
                << std::endl;
      return (2);
   }
   
   // distinguish between downloading only one board or all Diehl board
   int iRes = 0;
   if (g_config.GetControllerNumber () > 0)
   {
      if (! g_config.Quiet ())
      {
         std::cout << "Load controller number "
                   << g_config.GetControllerNumber ()
                   << "..."
                   << std::endl;
      }
      
      // check if the requested controller really exists as an Diehl board and
      // is the first port of its board
      CControllerList::const_iterator iterCtlr;
      iterCtlr = ctlrList.find (g_config.GetControllerNumber ());
      if (iterCtlr == ctlrList.end ())
      {
         std::cerr << "Controller number " << g_config.GetControllerNumber ()
                   << "\" does not exist or is no Diehl active ISDN board, unable to download"
                   << std::endl;
         return (2);
      }
      if (iterCtlr->GetPortIndex () > 0)
      {
         std::cerr << "Controller \"" << iterCtlr->GetControllerName ()
                   << "\", number "
                   << std::dec << iterCtlr->GetControllerNumber ()
                   << " is not first port of its board, unable to download"
                   << std::endl;
         return (2);
      }
      
      // check if there is a configuration entry for the board requested
      CConfigFile::CSectionIterator iterSect;
      iterSect = cfgFile.SectionFind
                    (std::string ("/" DAIC_CFG_SECT_BOARDS "/") +
                     iterCtlr->GetBoardName ());
      if (iterSect == cfgFile.SectionEnd ())
      {
         std::cerr << "No configuration entry found for board \""
                   << iterCtlr->GetBoardName ()
                   << "\", unable to perform download"
                   << std::endl;
         return (2);
      }
      
      // now perform the download operation for the addressed board
      iRes = LoadOneBoard (*iterCtlr, *iterSect, cfgFile);
   }
   else if (g_config.GetControllerName ().length () > 0)
   {
      if (! g_config.Quiet ())
      {
         std::cout << "Load controller with name \""
                   << g_config.GetControllerName ()
                   << "\"..."
                   << std::endl;
      }
      
      // check if the requested controller really exists as an Diehl board and
      // is the first port of its board
      CControllerList::const_iterator iterCtlr;
      iterCtlr = ctlrList.find (g_config.GetControllerName ());
      if (iterCtlr == ctlrList.end ())
      {
         iterCtlr = ctlrList.find_by_board_name
                       (g_config.GetControllerName ());
         if (iterCtlr == ctlrList.end ())
         {
            std::cerr << "Controller \"" << g_config.GetControllerName ()
                      << "\" does not exist or is no Diehl active ISDN board, unable to download"
                      << std::endl;
            return (2);
         }
      }
      if (iterCtlr->GetPortIndex () > 0)
      {
         std::cerr << "Controller \"" << iterCtlr->GetControllerName ()
                   << "\", number "
                   << std::dec << iterCtlr->GetControllerNumber ()
                   << " is not first port of its board, unable to download"
                   << std::endl;
         return (2);
      }
      
      // check if there is a configuration entry for the board requested
      CConfigFile::CSectionIterator iterSect;
      iterSect = cfgFile.SectionFind
                    (std::string ("/" DAIC_CFG_SECT_BOARDS "/") +
                     iterCtlr->GetBoardName ());
      if (iterSect == cfgFile.SectionEnd ())
      {
         std::cerr << "No configuration entry found for board \""
                   << iterCtlr->GetBoardName ()
                   << "\", unable to perform download"
                   << std::endl;
         return (2);
      }
      
      // now perform the download operation for the addressed board
      iRes = LoadOneBoard (*iterCtlr, *iterSect, cfgFile);
   }
   else
   {
      if (! g_config.Quiet ())
      {
         std::cout << "Load all " << ctlrList.size ()
                   << " Diehl active ISDN boards..."
                   << std::endl;
      }
      
      // first check if the configuration database contains non-existing boards
      CConfigFile::CSectionIterator iterSect;
      for (iterSect = cfgFile.SectionBegin ("/" DAIC_CFG_SECT_BOARDS);
           iterSect != cfgFile.SectionEnd ();
           ++iterSect)
      {
         if (ctlrList.find_by_board_name
                (iterSect->GetShortName ()) == ctlrList.end ())
         {
            std::cerr << "Configuration entry \""
                      << iterSect->GetShortName ()
                      << "\" denotes non-existing controller"
                      << std::endl;
            iRes = 2;
         }
      }
      
      // walk through all Diehl boards found
      CControllerList::const_iterator iterCtlr;
      int                             iRes2;
      for (iterCtlr = ctlrList.begin ();
           iterCtlr != ctlrList.end ();
           ++iterCtlr)
      {
         // leave out controllers that are not the first port of their board
         if (iterCtlr->GetPortIndex () > 0)
         {
            continue;
         }
   
         if (! g_config.Quiet ())
         {
            std::cout << "Load board with name \""
                      << iterCtlr->GetBoardName ()
                      << "\"..."
                      << std::endl;
         }
   
         try
         {
            // check if there is a configuration entry for the current board
            iterSect = cfgFile.SectionFind
                          (std::string ("/" DAIC_CFG_SECT_BOARDS "/") +
                           iterCtlr->GetBoardName ());
            if (iterSect == cfgFile.SectionEnd ())
            {
               std::cerr << "No configuration entry found for board \""
                         << iterCtlr->GetBoardName ()
                         << "\", unable to perform download"
                         << std::endl;
               continue;
            }
            
            // now perform the download operation for the current board
            iRes2 = LoadOneBoard (*iterCtlr, *iterSect, cfgFile);
            if (iRes == 0)
            {
               iRes = iRes2;
            }
         }
         catch (std::bad_alloc &)
         {
            throw;
         }
         catch (std::exception &e)
         {
            std::cerr << g_config.GetProgName ()
                      << ": Error performing download for board \""
                      << iterCtlr->GetBoardName ()
                      << "\": " << e.what ()
                      << std::endl;
            iRes = 2;
         }
      }
   }
   
   if (iRes == 0 && ! g_config.Quiet ())
   {
      std::cout << "Download complete." << std::endl;
   }
   
   return (iRes);
} // LoadBoards





/**
 * Disable one board or all boards.
 *
 * @param None.
 *
 * @retval 0                    The disabling operation was successful.
 * @retval Else                 Failure, none or not all boards could be
 *                              disabled.
 */

static int ResetBoards (void)
{
   // read in the existing configuration database
   CConfigFile cfgFile (g_config.GetConfigFileName ());
   
   // Create list of available Diehl controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No Diehl active ISDN controllers found, unable to perform reset"
                << std::endl;
      return (2);
   }
   
   // distinguish between resetting only one board or all Diehl board
   int iRes = 0;
   if (g_config.GetControllerNumber () > 0)
   {
      if (! g_config.Quiet ())
      {
         std::cout << "Reset controller number "
                   << g_config.GetControllerNumber ()
                   << "..."
                   << std::endl;
      }
      
      // check if the requested controller really exists as an Diehl board and
      // is the first port of its board
      CControllerList::const_iterator iterCtlr;
      iterCtlr = ctlrList.find (g_config.GetControllerNumber ());
      if (iterCtlr == ctlrList.end ())
      {
         std::cerr << "Controller number " << g_config.GetControllerNumber ()
                   << "\" does not exist or is no Diehl active ISDN board, unable to reset"
                   << std::endl;
         return (2);
      }
      if (iterCtlr->GetPortIndex () > 0)
      {
         std::cerr << "Controller \"" << iterCtlr->GetControllerName ()
                   << "\", number "
                   << std::dec << iterCtlr->GetControllerNumber ()
                   << " is not first port of its board, unable to reset"
                   << std::endl;
         return (2);
      }
      
      // check if there is a configuration entry for the board requested
      CConfigFile::CSectionIterator iterSect;
      iterSect = cfgFile.SectionFind
                    (std::string ("/" DAIC_CFG_SECT_BOARDS "/") +
                     iterCtlr->GetBoardName ());
      if (iterSect == cfgFile.SectionEnd ())
      {
         std::cerr << "No configuration entry found for board \""
                   << iterCtlr->GetBoardName ()
                   << "\", unable to perform reset"
                   << std::endl;
         return (2);;
      }
      
      // now perform the reset operation for the addressed board
      iRes = ResetOneBoard (*iterCtlr);
   }
   else if (g_config.GetControllerName ().length () > 0)
   {
      if (! g_config.Quiet ())
      {
         std::cout << "Reset board with name \""
                   << g_config.GetControllerName ()
                   << "\"..."
                   << std::endl;
      }
      
      // check if the requested controller really exists as an Diehl board and
      // is the first port of its board
      CControllerList::const_iterator iterCtlr;
      iterCtlr = ctlrList.find (g_config.GetControllerName ());
      if (iterCtlr == ctlrList.end ())
      {
         iterCtlr = ctlrList.find_by_board_name
                       (g_config.GetControllerName ());
         if (iterCtlr == ctlrList.end ())
         {
            std::cerr << "Controller \"" << g_config.GetControllerName ()
                      << "\" does not exist or is no Diehl active ISDN board, unable to reset"
                      << std::endl;
            return (2);
         }
      }
      if (iterCtlr->GetPortIndex () > 0)
      {
         std::cerr << "Controller \"" << iterCtlr->GetControllerName ()
                   << "\", number "
                   << std::dec << iterCtlr->GetControllerNumber ()
                   << " is not first port of its board, unable to reset"
                   << std::endl;
         return (2);
      }
      
      // check if there is a configuration entry for the board requested
      CConfigFile::CSectionIterator iterSect;
      iterSect = cfgFile.SectionFind
                    (std::string ("/" DAIC_CFG_SECT_BOARDS "/") +
                     iterCtlr->GetBoardName ());
      if (iterSect == cfgFile.SectionEnd ())
      {
         std::cerr << "No configuration entry found for board \""
                   << iterCtlr->GetBoardName ()
                   << "\", unable to perform reset"
                   << std::endl;
         return (2);
      }
      
      // now perform the reset operation for the addressed board
      iRes = ResetOneBoard (*iterCtlr);
   }
   else
   {
      if (! g_config.Quiet ())
      {
         std::cout << "Reset all " << ctlrList.size ()
                   << " Diehl active ISDN boards..."
                   << std::endl;
      }
      
      // first check if the configuration database contains non-existing boards
      CConfigFile::CSectionIterator iterSect;
      for (iterSect = cfgFile.SectionBegin ("/" DAIC_CFG_SECT_BOARDS);
           iterSect != cfgFile.SectionEnd ();
           ++iterSect)
      {
         if (ctlrList.find_by_board_name
                (iterSect->GetShortName ()) == ctlrList.end ())
         {
            std::cerr << "Configuration entry \""
                      << iterSect->GetShortName ()
                      << "\" denotes non-existing controller"
                      << std::endl;
            iRes = 2;
         }
      }
      
      // walk through all Diehl boards found
      CControllerList::const_iterator iterCtlr;
      int                             iRes2;
      for (iterCtlr = ctlrList.begin ();
           iterCtlr != ctlrList.end ();
           ++iterCtlr)
      {
         // leave out controllers that are not the first port of their board
         if (iterCtlr->GetPortIndex () > 0)
         {
            continue;
         }
   
         if (! g_config.Quiet ())
         {
            std::cout << "Reset board with name \""
                      << iterCtlr->GetBoardName ()
                      << "\"..."
                      << std::endl;
         }
         
         try
         {
            // check if there is a configuration entry for the current board
            iterSect = cfgFile.SectionFind
                          (std::string ("/" DAIC_CFG_SECT_BOARDS "/") +
                           iterCtlr->GetBoardName ());
            if (iterSect == cfgFile.SectionEnd ())
            {
               std::cerr << "No configuration entry found for board \""
                         << iterCtlr->GetBoardName ()
                         << "\", unable to perform reset"
                         << std::endl;
               continue;
            }
            
            // now perform the reset operation for the current board
            iRes2 = ResetOneBoard (*iterCtlr);
            if (iRes == 0)
            {
               iRes = iRes2;
            }
         }
         catch (std::bad_alloc &)
         {
            throw;
         }
         catch (std::exception &e)
         {
            std::cerr << g_config.GetProgName ()
                      << ": Error performing reset for board \""
                      << iterCtlr->GetBoardName ()
                      << "\": " << e.what ()
                      << std::endl;
            iRes = 2;
         }
      }
   }
   
   if (iRes == 0 && ! g_config.Quiet ())
   {
      std::cout << "Reset complete." << std::endl;
   }
   
   return (iRes);
} // ResetBoards





/**
 * Perform a download operation for a specific board.
 *
 * @param ctlrData              I: Information about the controller to
 *                                 download.
 * @param cfgSect               I: The configuration section for the board.
 * @param cfgFile               I: The complete configuration file containing
 *                                 the section at cfgSect.
 *
 * @retval 0                    The download operation was successful.
 * @retval Else                 Failure.
 */

static int LoadOneBoard
   (const CController           &ctlrData,
    const CConfigFile::CSection &cfgSect,
    CConfigFile                 &cfgFile)
{
   CConfigFile::CSectionIterator iter;
   std::string                   strBaseDir;
   std::string                   strBootCodeFileName;
   std::string                   strFirmwareFileName;
   int                           fdBootCode;
   int                           fdFirmware;
   struct stat                   statbuf;
   size_t                        nLenBootCode;
   size_t                        nLenFirmware;
   DaicBoardConfigData_t         configData;
   CAPICtlrDataBlock_t           aCtlrDataBlocks [3];
   unsigned                      uRes;
   
   // all files (boot code and firmware code) must lie under the same
   // directory
   iter = cfgFile.SectionFind ("/" DAIC_CFG_SECT_GLOBAL);
   if (iter != cfgFile.SectionEnd ())
   {
      strBaseDir = iter->GetKeyValueAsString (DAIC_CFG_KEY_BASE_DIRECTORY);
   }
   else
   {
      std::cerr << g_config.GetProgName ()
                << ": WARNING: Base directory not set in configuration file"
                << std::endl;
   }
   // be sure the directory name ends with a path separator character
   if (strBaseDir.length () <= 0 ||
       strBaseDir [strBaseDir.length () - 1] != '/')
   {
      strBaseDir += '/';
   }
   
   // create a data block for the board configuration
   CreateConfigData (cfgSect, cfgFile,
                     strBootCodeFileName, strFirmwareFileName, configData);
   
   // the file names must be prepended with the base directory name
   strBootCodeFileName.insert (0, strBaseDir);
   strFirmwareFileName.insert (0, strBaseDir);
   
   // open the file for the boot code
   fdBootCode = open (strBootCodeFileName.c_str (), O_RDONLY);
   if (fdBootCode == -1)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error opening boot code file \"" << strBootCodeFileName
                << "\": " << strerror (errno) << " (" << errno << ")"
                << std::endl;
      return (2);
   }
   if (fstat (fdBootCode, &statbuf) != 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error getting size of file \"" << strBootCodeFileName
                << "\": " << strerror (errno) << " (" << errno << ")"
                << std::endl;
      (void) close (fdBootCode);
      return (2);
   }
   nLenBootCode = (size_t) (statbuf.st_size);
   
   // open the file for the firmware code
   fdFirmware = open (strFirmwareFileName.c_str (), O_RDONLY);
   if (fdFirmware == -1)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error opening firmware file \""
                << strFirmwareFileName << "\": "
                << strerror (errno) << " (" << errno << ")" << std::endl;
      (void) close (fdBootCode);
      return (2);
   }
   if (fstat (fdFirmware, &statbuf) != 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error getting size of file \"" << strFirmwareFileName
                << "\": " << strerror (errno) << " (" << errno << ")"
                << std::endl;
      (void) close (fdFirmware);
      (void) close (fdBootCode);
      return (2);
   }
   nLenFirmware = (size_t) (statbuf.st_size);
   
   // prepare the download blocks
   bzero (aCtlrDataBlocks, sizeof (aCtlrDataBlocks));
   aCtlrDataBlocks [0].nLenDataBlock = nLenBootCode;
   aCtlrDataBlocks [0].paucDataBlock =
      (unsigned char *)
         mmap (NULL, nLenBootCode, PROT_READ, MAP_PRIVATE, fdBootCode, 0);
   if (aCtlrDataBlocks [0].paucDataBlock == MAP_FAILED)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error mapping boot code file content (\""
                << strBootCodeFileName << "\", " << nLenBootCode
                << " bytes) into memory: "
                << strerror (errno) << " (" << errno << ")" << std::endl;
      (void) close (fdFirmware);
      (void) close (fdBootCode);
      return (2);
   }
   aCtlrDataBlocks [1].nLenDataBlock = nLenFirmware;
   aCtlrDataBlocks [1].paucDataBlock =
      (unsigned char *)
         mmap (NULL, nLenFirmware, PROT_READ, MAP_PRIVATE, fdFirmware, 0);
   if (aCtlrDataBlocks [1].paucDataBlock == MAP_FAILED)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error mapping firmware file content (\""
                << strFirmwareFileName << "\", " << nLenFirmware
                << " bytes) into memory: "
                << strerror (errno) << " (" << errno << ")" << std::endl;
      (void) munmap (aCtlrDataBlocks [0].paucDataBlock,
                     aCtlrDataBlocks [0].nLenDataBlock);
      (void) close (fdFirmware);
      (void) close (fdBootCode);
      return (2);
   }
   aCtlrDataBlocks [2].nLenDataBlock = sizeof (configData);
   aCtlrDataBlocks [2].paucDataBlock = (unsigned char *) &configData;

   // now finally perform the download request
   uRes = capi20_reset_ctlr
             (ctlrData.GetControllerNumber (), 3, aCtlrDataBlocks);
   (void) munmap (aCtlrDataBlocks [1].paucDataBlock, 
                  aCtlrDataBlocks [1].nLenDataBlock);
   (void) close (fdFirmware);
   (void) munmap (aCtlrDataBlocks [0].paucDataBlock, 
                  aCtlrDataBlocks [0].nLenDataBlock);
   (void) close (fdBootCode);
   if (uRes == CRE_NO_RIGHTS_TO_RESET)
   {
      std::cerr << "Error in download operation for board \""
                << ctlrData.GetBoardName () << "\", number "
                << std::dec << ctlrData.GetControllerNumber ()
                << ": Insufficient rights, must have root privileges"
                << std::endl;
      return (2);
   }
   else if (uRes != CAPI_OK)
   {
      std::cerr << "Error in download operation for board \""
                << ctlrData.GetBoardName () << "\", number "
                << std::dec << ctlrData.GetControllerNumber () << ": 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
                << std::endl;
      return (2);
   }
   
   return (0);
} /* LoadOneBoard */





/**
 * Create a configuration data record for downloading a board.
 *
 * @param cfgSect               I: The configuration section for the board.
 * @param cfgFile               I: The complete configuration file containing
 *                                 the section at cfgSect.
 * @param strBootCodeFileName   O: On successful return the boot code file name
 *                                 to use for the board is stored here.
 * @param strFirmwareFileName   O: On successful return the firmware file name
 *                                 to use is stored here.
 * @param configData            O: On successful return the configuration data
 *                                 is stored here.
 *
 * @return Nothing.
 */

static void CreateConfigData
   (const CConfigFile::CSection &cfgSect,
    CConfigFile                 &cfgFile,
    std::string                 &strBootCodeFileName,
    std::string                 &strFirmwareFileName,
    DaicBoardConfigData_t       &configData)
{
   std::string                   strDProtocol;
   bool                          fPrimary;

   bzero (&configData, sizeof (configData));
   
   // first we need the D-channel protocol to create the firmware file name to
   // use
   strDProtocol = cfgSect.GetKeyValueAsString (DAIC_CFG_KEY_DPROTOCOL);
   if (strDProtocol.length () == 0)
   {
      throw std::runtime_error
               ("D-channel protocol not found in configuration section");
   }

   // dermine the board type from the short section name, i.e. the board name
   if (cfgSect.GetShortName ().find ("S2m") != std::string::npos)
   {
      strBootCodeFileName  = "prload.bin";
      strFirmwareFileName  = std::string ("te_") + strDProtocol + ".p";
      fPrimary = true;
   }
   else if (cfgSect.GetShortName ().find ("Quadro") != std::string::npos)
   {
      strBootCodeFileName  = "dnload.bin";
      strFirmwareFileName  = std::string ("te_") + strDProtocol + ".sq";
      fPrimary = false;
   }
   else if (cfgSect.GetShortName ().find ("SCOM") != std::string::npos)
   {
      strBootCodeFileName  = "dnload.bin";
      strFirmwareFileName  = std::string ("te_") + strDProtocol + ".sy";
      fPrimary = false;
   }
   else if (cfgSect.GetShortName ().find ("Sx") != std::string::npos)
   {
      strBootCodeFileName  = "dnload.bin";
      strFirmwareFileName  = std::string ("te_") + strDProtocol + ".sx";
      fPrimary = false;
   }
   else
   {
      strBootCodeFileName  = "dnload.bin";
      strFirmwareFileName  = std::string ("te_") + strDProtocol + ".bin";
      fPrimary = false;
   }
   
   // fill in the board configuration data fields
   configData.ucNt2 =
      (unsigned char)
         cfgSect.GetKeyValueAsUnsignedNumber<unsigned> (DAIC_CFG_KEY_NT2);
   if (configData.ucNt2)
   {
      configData.ucTei =
         (unsigned char) ((cfgSect.GetKeyValueAsUnsignedNumber<unsigned>
                              (DAIC_CFG_KEY_TEI) << 1) | 0x01);
   }
   else
   {
      configData.ucTei = 0x00;
   }
   configData.ucPermanent =
      (unsigned char)
         cfgSect.GetKeyValueAsUnsignedNumber<unsigned>
            (DAIC_CFG_KEY_PERMANENT);
   configData.ucStableL2 =
      (unsigned char)
         cfgSect.GetKeyValueAsUnsignedNumber<unsigned> (DAIC_CFG_KEY_LAYER2);
   configData.ucProtVersion =
      (unsigned char)
         cfgSect.GetKeyValueAsUnsignedNumber<unsigned>
            (DAIC_CFG_KEY_PROTOCOL_VERSION);
   configData.ucMemBootOpt =
      (unsigned char)
         cfgSect.GetKeyValueAsUnsignedNumber<unsigned>
            (DAIC_CFG_KEY_MEMORY_TEST);
   if (fPrimary)
   {
      configData.ucLowChannel =
         (unsigned char)
            cfgSect.GetKeyValueAsUnsignedNumber<unsigned>
               (DAIC_CFG_KEY_LOW_CHANNEL);
      configData.ucCrc4 =
         (unsigned char)
            cfgSect.GetKeyValueAsUnsignedNumber<unsigned> (DAIC_CFG_KEY_CRC4);
   }
   
   strlcpy ((char *) configData.aucOad1,
            cfgSect.GetKeyValueAsString
               (DAIC_CFG_KEY_SPID1).c_str (),
            sizeof (configData.aucOad1));
   strlcpy ((char *) configData.aucOsa1,
            cfgSect.GetKeyValueAsString
               (DAIC_CFG_KEY_SPID1).c_str (),
            sizeof (configData.aucOsa1));
   strlcpy ((char *) configData.aucSpid1,
            cfgSect.GetKeyValueAsString
               (DAIC_CFG_KEY_SPID1).c_str (),
            sizeof (configData.aucSpid1));
   strlcpy ((char *) configData.aucOad2,
            cfgSect.GetKeyValueAsString
               (DAIC_CFG_KEY_SPID2).c_str (),
            sizeof (configData.aucOad2));
   strlcpy ((char *) configData.aucOsa2,
            cfgSect.GetKeyValueAsString
               (DAIC_CFG_KEY_SPID2).c_str (),
            sizeof (configData.aucOsa2));
   strlcpy ((char *) configData.aucSpid2,
            cfgSect.GetKeyValueAsString
               (DAIC_CFG_KEY_SPID2).c_str (),
            sizeof (configData.aucSpid2));

} // CreateConfigData





/**
 * Disable a specific board.
 *
 * @param ctlrData              I: Information about the controller to disable.
 *
 * @retval 0                    The disabling operation was successful.
 * @retval Else                 Failure.
 */

static int ResetOneBoard
   (const CController &ctlrData)
{
   unsigned uRes;
   
   uRes = capi20_reset_ctlr (ctlrData.GetControllerNumber (), 0, NULL);
   
   if (uRes == CRE_NO_RIGHTS_TO_RESET)
   {
      std::cerr << "Error in reset operation for board \""
                << ctlrData.GetBoardName () << "\", number "
                << std::dec << ctlrData.GetControllerNumber ()
                << ": Insufficient rights, must have root privileges"
                << std::endl;
      return (2);
   }
   else if (uRes != CAPI_OK)
   {
      std::cerr << "Error in reset operation for board \""
                << ctlrData.GetBoardName () << "\", number "
                << std::dec << ctlrData.GetControllerNumber () << ": 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
                << std::endl;
      return (2);
   }
   
   return (0);
} /* ResetOneboard */
