/**
 * @file ix1actl.cc
 *
 * Ix1aControl - Main module for the IX1 active ISDN boards control program.
 *
 * Copyright: 2005 Thomas Wintergerst. All rights reserved.
 *
 * $Id: ix1actl.cc,v 1.1.2.1 2006/02/28 21:35:48 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     IX1 active ISDN boards control program $
 * @date        03.01.2005
 * @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 __IX1ACTL__

// Local includes
#include "config.h"
#include "ctlrlist.h"
#include "cfgfile.h"
#include "ix1a_board_params.h"
#include "ix1a_cfgdefs.h"





// === Private definitions ===============================================





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





// === Definitions for public declarations ===============================





// === Prototypes for private functions ==================================





/**
 * Print out a list of available controllers.
 *
 * This function will print a short list of available CAPI controllers. For IX1
 * 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 boardData             O: The board data record to be filled.
 *
 * @return Nothing.
 */
static void SetBoardDataDefaults
   (Ix1aBoardParameters_t &boardData);

/**
 * Add entries for a board data structure to a configuration database.
 *
 * @param strBoardName          I: The board name for its base section.
 * @param strBootCodeFileName   I: The file name for the boot code file,
 *                                 relative to the base directory in the global
 *                                 section of the configuration database. If no
 *                                 no boot code file is necessary (Primary),
 *                                 this string is empty. No configuration entry
 *                                 shall then be created for the boot code file
 *                                 name.
 * @param strFirmwareFileName   I: The file name for the firmware file,
 *                                 relative to the base directory in the global
 *                                 section of the configuration database.
 * @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           &strBootCodeFileName,
    const std::string           &strFirmwareFileName,
    const Ix1aBoardParameters_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 configData            O: On successful return the configuration data
 *                                 is stored here. The number of line parameter
 *                                 structures filled is returned in
 *                                 configData.dwNumLines.
 *
 * @retval true                 The configuration data block was filled
 *                              successfully.
 * @retval false                Failure, not configuration data block could be
 *                              created.
 */
static bool CreateConfigData
   (const CConfigFile::CSection &cfgSect,
    CConfigFile                 &cfgFile,
    Ix1aBoardParameters_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);





// === Implementation of class members ===================================





// === Implementation 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





// === Implementation of private functions ===============================





/**
 * Print out a list of available controllers.
 *
 * This function will print a short list of available CAPI controllers. For IX1
 * 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 IX1 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 IX1 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
                 ("/" IX1A_CFG_SECT_GLOBAL, IX1A_COMMENT_SECT_GLOBAL);
   iterSect->AddKeyValuePair (IX1A_CFG_KEY_BASE_DIRECTORY,
                              g_config.GetBaseDirectory (),
                              IX1A_COMMENT_KEY_BASE_DIRECTORY);

   // create list of available IX1 controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cout << "No IX1 active ISDN controllers found, no configuration file created"
                << std::endl;
      return (2);
   }
   if (! g_config.Quiet ())
   {
      std::cout << "Found " << ctlrList.size ()
                << " IX1 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 IX1 active ISDN boards..."
                << std::endl;
   }
   
   // read in the existing configuration database
   CConfigFile cfgFile (g_config.GetConfigFileName ());
   
   // Create list of available IX1 controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No IX1 active ISDN controllers found, will not perform update"
                << std::endl;
      return (2);
   }
   if (! g_config.Quiet ())
   {
      std::cout << "Found " << ctlrList.size ()
                << " IX1 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 (IX1A_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 (IX1A_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 ("/" IX1A_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
   Ix1aBoardParameters_t boardData;
   SetBoardDataDefaults (boardData);
   
   // determine the board type according to the controller name and modify the
   // necessary settings
   std::string strBootCodeFileName;
   std::string strFirmwareFileName;
   if (ctlr.GetBoardName ().find ("Basic") != std::string::npos)
   {
      strBootCodeFileName  = "ixboot.scr";
      strFirmwareFileName  = "ixb-4m.bin";
      boardData.dwNumLines = 1;
   }
   else if (ctlr.GetBoardName ().find ("Octo") != std::string::npos)
   {
      strBootCodeFileName  = "ixboot.scr";
      strFirmwareFileName  = "ixo.bin";
      boardData.dwNumLines = 4;
   }
   else if (ctlr.GetBoardName ().find ("Primary") != std::string::npos)
   {
      strBootCodeFileName.clear ();
      strFirmwareFileName  = "ixpp.bin";
      boardData.dwNumLines = 1;
      boardData.aLineParams [0].interface.dwLineAccess = 2;
      boardData.aLineParams [0].dchan.dwTeiType = 2;
      boardData.aLineParams [0].dchan.dwTeiValue = 0;
   }
   else if (ctlr.GetBoardName ().find ("MMod") != std::string::npos)
   {
      strBootCodeFileName  = "ixmboot.scr";
      strFirmwareFileName  = "ixm-4m.bin";
      boardData.dwNumLines = 0;
   }
   else
   {
      std::ostringstream oss;
      oss << "Unable to detect board type for board \""
          << ctlr.GetBoardName ()
          << "\", cannot provide board configuration";
      throw std::runtime_error (oss.str ());
   }
   
   // finally add sections and values to the configuration database for the
   // board
   AddBoardConfigToConfigDb
      (ctlr.GetBoardName (), strBootCodeFileName, strFirmwareFileName,
       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 boardData             O: The board data record to be filled.
 *
 * @return Nothing.
 */

static void SetBoardDataDefaults
   (Ix1aBoardParameters_t &boardData)
{
   memset (&boardData, 0, sizeof (boardData));
   
   // fill in the main fields; first assume no line ports, but initialise them
   // nevertheless
   boardData.dwVersion = IX1A_BOARD_CONFIG_VERSION;
   boardData.dwSize    = sizeof (boardData);

   // fill in the general fields
   boardData.general.dwSize = sizeof (boardData.general);
   boardData.general.dwMvipType = 0;
   boardData.general.dwCodingLaw = 0;
   boardData.general.dwMultimodemCountryCode = 0;
   
   // fill in the fax fields (none defined)
   boardData.fax.dwSize = sizeof (boardData.fax);
   
   // fill in the modem fields (none defined)
   boardData.modem.dwSize = sizeof (boardData.modem);

   // fill in the compression fields
   boardData.comp.dwSize = sizeof (boardData.comp);
   boardData.comp.dwCompMode = 4;
   
   // set the number of lines
   boardData.dwNumLines = 0;

   // for every line of the board fill the line fields
   size_t n;
   for (n = 0;
        (size_t) n < sizeof (boardData.aLineParams) /
                        sizeof (boardData.aLineParams [0]);
        ++n)
   {
      // fill in the main part
      Ix1aBoardLineConfigParameters_t *pLineParams =
         &(boardData.aLineParams [n]);
      pLineParams->dwSize = sizeof (*pLineParams);
      
      // fill in the line interface parameters
      pLineParams->interface.dwSize = sizeof (pLineParams->interface);
      pLineParams->interface.dwLineAccess = 1;
      pLineParams->interface.dwLineType = 1;
      pLineParams->interface.dwLeasedLineType = 0;
      pLineParams->interface.dwNtTeSide = 1;
         
      pLineParams->interface.dwClockGenerator = 1;
      pLineParams->interface.dwLineBitrate = 1;
      pLineParams->interface.dwFirstBChan = 0;
      pLineParams->interface.dwLastBChan = 0;
      pLineParams->interface.dwBChanMask = 0;
      pLineParams->interface.dwBChanSelection = 0;
      pLineParams->interface.dwLineCode = 0;
      pLineParams->interface.dwFramingFormat = 0;
      pLineParams->interface.dwDsxPreEmphasis = 0;
      
      // fill in the D-channel parameters
      pLineParams->dchan.dwSize = sizeof (pLineParams->dchan);
      pLineParams->dchan.dwDProtocol = 2;
      pLineParams->dchan.dwSignalingMode = 0;
      pLineParams->dchan.dwTeiType = 1;
      pLineParams->dchan.dwTeiValue = 0;
      
      // fill in the service profile parameters (leave everything unset)
      pLineParams->serviceProfile.dwSize = sizeof (pLineParams->serviceProfile);
      
      // fill in the SAS parameters (not supported, leave set to null)
      pLineParams->sas.dwSize = sizeof (pLineParams->sas);
   }
   
} // SetBoardDataDefaults





/**
 * Add entries for a board data structure to a configuration database.
 *
 * @param strBoardName          I: The board name for its base section.
 * @param strBootCodeFileName   I: The file name for the boot code file,
 *                                 relative to the base directory in the global
 *                                 section of the configuration database. If no
 *                                 no boot code file is necessary (Primary),
 *                                 this string is empty. No configuration entry
 *                                 shall then be created for the boot code file
 *                                 name.
 * @param strFirmwareFileName   I: The file name for the firmware file,
 *                                 relative to the base directory in the global
 *                                 section of the configuration database.
 * @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           &strBootCodeFileName,
    const std::string           &strFirmwareFileName,
    const Ix1aBoardParameters_t &boardData,
    CConfigFile                 &cfgFile)
{
   bool                          fPrimary;
   CConfigFile::CSectionIterator iter;
   std::string                   strSect;
   
   // detect if the board is a Primary board (some entries are only for Primary
   // boards or only for non-Primary boards)
   if (strBoardName.find ("Primary") == std::string::npos)
   {
      fPrimary = false;
   }
   else
   {
      fPrimary = true;
   }

   // create the board section and add the download file names
   strSect = std::string ("/" IX1A_CFG_SECT_BOARDS) + "/" + strBoardName;
   iter = cfgFile.SectionAdd (strSect);
   if (strBootCodeFileName.length () > 0)
   {
      iter->AddKeyValuePair (IX1A_CFG_KEY_BOOT_FILE, strBootCodeFileName,
                             IX1A_COMMENT_KEY_BOOT_FILE);
   }
   iter->AddKeyValuePair (IX1A_CFG_KEY_FIRMWARE_FILE, strFirmwareFileName,
                          IX1A_COMMENT_KEY_FIRMWARE_FILE);
   
   // create the general board configuration section
   iter = cfgFile.SectionAdd (strSect + "/" + IX1A_CFG_SECT_GENERAL,
                              IX1A_COMMENT_SECT_GENERAL);
   iter->AddKeyValuePair (IX1A_CFG_KEY_MVIP_TYPE,
                          boardData.general.dwMvipType,
                          IX1A_COMMENT_KEY_MVIP_TYPE);
   iter->AddKeyValuePair (IX1A_CFG_KEY_VOICE_CODING,
                          boardData.general.dwCodingLaw,
                          IX1A_COMMENT_KEY_VOICE_CODING);
   iter->AddKeyValuePair (IX1A_CFG_KEY_COUNTRY_VERSION,
                          boardData.general.dwMultimodemCountryCode,
                          IX1A_COMMENT_KEY_COUNTRY_VERSION);
   iter->AddKeyValuePair (IX1A_CFG_KEY_COMPRESSION,
                          boardData.comp.dwCompMode,
                          IX1A_COMMENT_KEY_COMPRESSION);
   
   // create the line(s) section(s)
   size_t n;
   for (n = 0; n < boardData.dwNumLines; ++n)
   {
      const Ix1aBoardLineConfigParameters_t *pLineParams =
         &(boardData.aLineParams [n]);

      // create the line access section
      std::ostringstream ossLineSect;
      ossLineSect << strSect << '/' << IX1A_CFG_SECT_PORT << n + 1 << '/';
      iter = cfgFile.SectionAdd
                (ossLineSect.str () + IX1A_CFG_SECT_LINE_ACCESS,
                 IX1A_COMMENT_SECT_LINE_ACCESS);
      iter->AddKeyValuePair (IX1A_CFG_KEY_DCHAN_PROTOCOL,
                             pLineParams->dchan.dwDProtocol,
                             IX1A_COMMENT_KEY_DCHAN_PROTOCOL);
      iter->AddKeyValuePair (IX1A_CFG_KEY_LINE_ACCESS,
                             pLineParams->interface.dwLineAccess,
                             IX1A_COMMENT_KEY_LINE_ACCESS);
      iter->AddKeyValuePair (IX1A_CFG_KEY_LINE_TYPE,
                             pLineParams->interface.dwLineType,
                             IX1A_COMMENT_KEY_LINE_TYPE);
      iter->AddKeyValuePair (IX1A_CFG_KEY_LEASED_LINE_TYPE,
                             pLineParams->interface.dwLeasedLineType,
                             IX1A_COMMENT_KEY_LEASED_LINE_TYPE);
      iter->AddKeyValuePair (IX1A_CFG_KEY_TEI_TYPE,
                             pLineParams->dchan.dwTeiType,
                             IX1A_COMMENT_KEY_TEI_TYPE);
      iter->AddKeyValuePair (IX1A_CFG_KEY_TEI_VALUE,
                             pLineParams->dchan.dwTeiValue,
                             IX1A_COMMENT_KEY_TEI_VALUE);
      iter->AddKeyValuePair (IX1A_CFG_KEY_NT_TE_MODE,
                             pLineParams->interface.dwNtTeSide,
                             IX1A_COMMENT_KEY_NT_TE_MODE);
      
      // create the Primary-specific section
      if (fPrimary)
      {
         iter = cfgFile.SectionAdd
                   (ossLineSect.str () + IX1A_CFG_SECT_PMX_SETTINGS,
                    IX1A_COMMENT_SECT_PMX_SETTINGS);
         iter->AddKeyValuePair (IX1A_CFG_KEY_CLOCK_GENERATOR,
                                pLineParams->interface.dwClockGenerator,
                                IX1A_COMMENT_KEY_CLOCK_GENERATOR);
         iter->AddKeyValuePair (IX1A_CFG_KEY_FIRST_BCHAN,
                                pLineParams->interface.dwFirstBChan,
                                IX1A_COMMENT_KEY_FIRST_BCHAN);
         iter->AddKeyValuePair (IX1A_CFG_KEY_LAST_BCHAN,
                                pLineParams->interface.dwLastBChan,
                                IX1A_COMMENT_KEY_LAST_BCHAN);
         iter->AddKeyValuePair (IX1A_CFG_KEY_BCHAN_SELECTION,
                                pLineParams->interface.dwBChanSelection,
                                IX1A_COMMENT_KEY_BCHAN_SELECTION);
         iter->AddKeyValuePair (IX1A_CFG_KEY_LINE_RATE,
                                pLineParams->interface.dwLineBitrate,
                                IX1A_COMMENT_KEY_LINE_RATE);
         iter->AddKeyValuePair (IX1A_CFG_KEY_LINE_CODE,
                                pLineParams->interface.dwLineCode,
                                IX1A_COMMENT_KEY_LINE_CODE);
         iter->AddKeyValuePair (IX1A_CFG_KEY_FRAMING_FORMAT,
                                pLineParams->interface.dwFramingFormat,
                                IX1A_COMMENT_KEY_FRAMING_FORMAT);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DSX_PRE_EMPHASIS,
                                pLineParams->interface.dwDsxPreEmphasis,
                                IX1A_COMMENT_KEY_DSX_PRE_EMPHASIS);
      }
      
      // create the SPID settings section
      if (! fPrimary)
      {
         iter = cfgFile.SectionAdd
                   (ossLineSect.str () + IX1A_CFG_SECT_SPID_SETTINGS,
                    IX1A_COMMENT_SECT_SPID_SETTINGS);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SPID1_DATA_ON,
                                pLineParams->serviceProfile.dwSpid1DataOn);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SPID2_DATA_ON,
                                pLineParams->serviceProfile.dwSpid2DataOn);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SPID1_VOICE_ON,
                                pLineParams->serviceProfile.dwSpid1VoiceOn);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SPID2_VOICE_ON,
                                pLineParams->serviceProfile.dwSpid2VoiceOn);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SIGNAL_DN2_AS_DN1,
                                pLineParams->serviceProfile.dwSignalDN2AsPrimaryDN1);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SIGNAL_ADD_DN1_AS_DN1,
                                pLineParams->serviceProfile.dwSignalAddDN1AsPrimaryDN1);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SIGNAL_ADD_DN2_AS_DN2,
                                pLineParams->serviceProfile.dwSignalAddDN2AsPrimaryDN2);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SPID1,
                                pLineParams->serviceProfile.szSpid1);
         iter->AddKeyValuePair (IX1A_CFG_KEY_SPID2,
                                pLineParams->serviceProfile.szSpid2);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN1,
                                pLineParams->serviceProfile.aszDn1 [0]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN2,
                                pLineParams->serviceProfile.aszDn2 [0]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN1_ADD1,
                                pLineParams->serviceProfile.aszDn1 [1]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN1_ADD2,
                                pLineParams->serviceProfile.aszDn1 [2]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN1_ADD3,
                                pLineParams->serviceProfile.aszDn1 [3]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN1_ADD4,
                                pLineParams->serviceProfile.aszDn1 [4]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN2_ADD1,
                                pLineParams->serviceProfile.aszDn2 [1]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN2_ADD2,
                                pLineParams->serviceProfile.aszDn2 [2]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN2_ADD3,
                                pLineParams->serviceProfile.aszDn2 [3]);
         iter->AddKeyValuePair (IX1A_CFG_KEY_DN2_ADD4,
                                pLineParams->serviceProfile.aszDn2 [4]);
      }
   }
   
} // 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 IX1 controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No IX1 active ISDN controllers found, unable to perform download"
                << std::endl;
      return (2);
   }
   
   // distinguish between downloading only one board or all IX1 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 IX1 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 IX1 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 ("/" IX1A_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 board with name \""
                   << g_config.GetControllerName ()
                   << "\"..."
                   << std::endl;
      }
      
      // check if the requested controller really exists as an IX1 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 IX1 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 ("/" IX1A_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 ()
                   << " IX1 active ISDN boards..."
                   << std::endl;
      }
      
      // first check if the configuration database contains non-existing boards
      CConfigFile::CSectionIterator iterSect;
      for (iterSect = cfgFile.SectionBegin ("/" IX1A_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 IX1 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 ("/" IX1A_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 IX1 controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No IX1 active ISDN controllers found, unable to perform reset"
                << std::endl;
      return (2);
   }
   
   // distinguish between resetting only one board or all IX1 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 IX1 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 IX1 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 ("/" IX1A_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 IX1 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 IX1 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 ("/" IX1A_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 ()
                   << " IX1 active ISDN boards..."
                   << std::endl;
      }
      
      // first check if the configuration database contains non-existing boards
      CConfigFile::CSectionIterator iterSect;
      for (iterSect = cfgFile.SectionBegin ("/" IX1A_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 IX1 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 ("/" IX1A_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;
   Ix1aBoardParameters_t         configData;
   CAPICtlrDataBlock_t           aCtlrDataBlocks [3];
   size_t                        nNumBlocks;
   unsigned                      uRes;
   
   // all files (boot code and firmware code) must lie under the same
   // directory
   iter = cfgFile.SectionFind ("/" IX1A_CFG_SECT_GLOBAL);
   if (iter != cfgFile.SectionEnd ())
   {
      strBaseDir = iter->GetKeyValueAsString (IX1A_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 += '/';
   }
   
   // open the file for the boot code
   iter = cfgFile.SectionFind (cfgSect.GetFullName () +
                               "/" IX1A_CFG_SECT_GENERAL);
   if (iter == cfgFile.SectionEnd ())
   {
      std::cerr << g_config.GetProgName ()
                << ": ERROR: Section \""
                << cfgSect.GetFullName () << '/' << IX1A_CFG_SECT_GENERAL
                << "\" not found in configuration file"
                << std::endl;
      return (EINVAL);
   }
   strBootCodeFileName = cfgSect.GetKeyValueAsString (IX1A_CFG_KEY_BOOT_FILE);
   if (strBootCodeFileName.length () == 0)
   {
      // a Primary board does not need any boot code, an empty block will be
      // sent instead
      nLenBootCode = 0;
      fdBootCode   = -1;
   }
   else
   {
      strBootCodeFileName = strBaseDir + strBootCodeFileName;
      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
   strFirmwareFileName = strBaseDir +
                         cfgSect.GetKeyValueAsString
                            (IX1A_CFG_KEY_FIRMWARE_FILE);
   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 first two download blocks for the boot and firmware code
   bzero (aCtlrDataBlocks, sizeof (aCtlrDataBlocks));
   aCtlrDataBlocks [0].nLenDataBlock = nLenBootCode;
   if (nLenBootCode != 0 && fdBootCode != -1)
   {
      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);
      if (fdBootCode != -1)
      {
         (void) close (fdBootCode);
      }
      return (2);
   }
   nNumBlocks = 2;
   
   // create a data block for the board configuration
   if (CreateConfigData (cfgSect, cfgFile, configData))
   {
      aCtlrDataBlocks [nNumBlocks].nLenDataBlock = le32toh (configData.dwSize);
      aCtlrDataBlocks [nNumBlocks].paucDataBlock =
         (unsigned char *) &configData;
      ++nNumBlocks;
   }
   
   // now finally perform the download request
   uRes = capi20_reset_ctlr (ctlrData.GetControllerNumber (),
                             nNumBlocks, aCtlrDataBlocks);
   (void) munmap (aCtlrDataBlocks [1].paucDataBlock, 
                  aCtlrDataBlocks [1].nLenDataBlock);
   (void) close (fdFirmware);
   if (fdBootCode != -1)
   {
      (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 configData            O: On successful return the configuration data
 *                                 is stored here.
 *
 * @retval true                 The configuration data block was filled
 *                              successfully.
 * @retval false                Failure, not configuration data block could be
 *                              created.
 */

static bool CreateConfigData
   (const CConfigFile::CSection &cfgSect,
    CConfigFile                 &cfgFile,
    Ix1aBoardParameters_t       &configData)
{
   bool                          fPrimary;
   size_t                        nNumLines;
   CConfigFile::CSectionIterator iter;

   bzero (&configData, sizeof (configData));
   
   // dermine the board type from the short section name
   if (cfgSect.GetShortName ().find ("Basic") != std::string::npos)
   {
      fPrimary = false;
      nNumLines = 1;
   }
   else if (cfgSect.GetShortName ().find ("Octo") != std::string::npos)
   {
      fPrimary = false;
      nNumLines = 4;
   }
   else if (cfgSect.GetShortName ().find ("Primary") != std::string::npos)
   {
      fPrimary = true;
      nNumLines = 1;
   }
   else if (cfgSect.GetShortName ().find ("MMod") != std::string::npos)
   {
      fPrimary = false;
      nNumLines = 0;
   }
   else
   {
      std::cerr << g_config.GetProgName ()
                << ": WARNING: Unable to detect board type for section name \""
                << cfgSect.GetShortName ()
                << "\", cannot provide board configuration"
                << std::endl;
      return (false);
   }
   
   // fill in the main fields
   configData.dwVersion = htole32 (IX1A_BOARD_CONFIG_VERSION);
   configData.dwSize    = htole32 (sizeof (configData));

   // fill in the general fields
   configData.general.dwSize =
      htole32 (sizeof (configData.general));
   iter = cfgFile.SectionFind
             (cfgSect.GetFullName () + "/" + IX1A_CFG_SECT_GENERAL);
   if (iter != cfgFile.SectionEnd ())
   {
      configData.general.dwMvipType =
         htole32 (iter->GetKeyValueAsUnsignedNumber<u_int32_t>
                     (IX1A_CFG_KEY_MVIP_TYPE));
      configData.general.dwCodingLaw =
         htole32 (iter->GetKeyValueAsUnsignedNumber<u_int32_t>
                     (IX1A_CFG_KEY_VOICE_CODING));
      configData.general.dwMultimodemCountryCode =
         htole32 (iter->GetKeyValueAsUnsignedNumber<u_int32_t>
                     (IX1A_CFG_KEY_COUNTRY_VERSION));
   }
   else
   {
      std::cerr << g_config.GetProgName ()
                << ": WARNING: Section \""
                << cfgSect.GetFullName () << '/' << IX1A_CFG_SECT_GENERAL
                << "\" does not exist"
                << std::endl;
   }
   
   // fill in the fax fields (none defined)
   configData.fax.dwSize = htole32 (sizeof (configData.fax));
   
   // fill in the modem fields (none defined)
   configData.modem.dwSize = htole32 (sizeof (configData.modem));

   // fill in the compression fields
   configData.comp.dwSize = htole32 (sizeof (configData.comp));
   if (iter != cfgFile.SectionEnd ())
   {
      configData.comp.dwCompMode =
         htole32 (iter->GetKeyValueAsUnsignedNumber<u_int32_t>
                     (IX1A_CFG_KEY_COMPRESSION));
   }
   
   // set the number of lines
   configData.dwNumLines = htole32 (nNumLines);

   // for every line of the board fill the line fields
   size_t n;
   for (n = 0; (size_t) n < nNumLines; ++n)
   {
      // access the configuration sections for the current line
      std::ostringstream ossLine;
      CConfigFile::CSectionIterator iterLineAcc;
      CConfigFile::CSectionIterator iterPmx;
      CConfigFile::CSectionIterator iterSpid;
      ossLine << cfgSect.GetFullName () << '/' << IX1A_CFG_SECT_PORT << n + 1;
      iterLineAcc = cfgFile.SectionFind
                       (ossLine.str () + '/' + IX1A_CFG_SECT_LINE_ACCESS);
      iterPmx = cfgFile.SectionFind
                   (ossLine.str () + '/'+ IX1A_CFG_SECT_PMX_SETTINGS);
      iterSpid = cfgFile.SectionFind
                    (ossLine.str () + '/' + IX1A_CFG_SECT_SPID_SETTINGS);

      // fill in the main part
      Ix1aBoardLineConfigParameters_t *pLineParams =
         &(configData.aLineParams [n]);
      pLineParams->dwSize = htole32 (sizeof (*pLineParams));
      
      // fill in the line interface parameters
      pLineParams->interface.dwSize =
         htole32 (sizeof (pLineParams->interface));
      if (iterLineAcc != cfgFile.SectionEnd ())
      {
         pLineParams->interface.dwLineAccess =
            htole32 (iterLineAcc->GetKeyValueAsUnsignedNumber<u_int32_t>
                        (IX1A_CFG_KEY_LINE_ACCESS));
         pLineParams->interface.dwLineType =
            htole32 (iterLineAcc->GetKeyValueAsUnsignedNumber<u_int32_t>
                        (IX1A_CFG_KEY_LINE_TYPE));
         pLineParams->interface.dwLeasedLineType =
            htole32 (iterLineAcc->GetKeyValueAsUnsignedNumber<u_int32_t>
                        (IX1A_CFG_KEY_LEASED_LINE_TYPE));
         pLineParams->interface.dwNtTeSide =
            htole32 (iterLineAcc->GetKeyValueAsUnsignedNumber<u_int32_t>
                        (IX1A_CFG_KEY_NT_TE_MODE));
         
         if (fPrimary && iterPmx != cfgFile.SectionEnd ())
         {
            pLineParams->interface.dwClockGenerator =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_CLOCK_GENERATOR));
            pLineParams->interface.dwLineBitrate =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_LINE_RATE));
            pLineParams->interface.dwFirstBChan =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_FIRST_BCHAN));
            pLineParams->interface.dwLastBChan =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_LAST_BCHAN));
            pLineParams->interface.dwBChanMask = 0;
            pLineParams->interface.dwBChanSelection =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_BCHAN_SELECTION));
            pLineParams->interface.dwLineCode =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_LINE_CODE));
            pLineParams->interface.dwFramingFormat =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_FRAMING_FORMAT));
            pLineParams->interface.dwDsxPreEmphasis =
               htole32 (iterPmx->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_DSX_PRE_EMPHASIS));
         }
         else if (fPrimary && iterPmx == cfgFile.SectionEnd ())
         {
            std::cerr << g_config.GetProgName ()
                      << ": WARNING: Section \""
                      << ossLine.str () << '/' << IX1A_CFG_SECT_PMX_SETTINGS
                      << "\" does not exist"
                      << std::endl;
         }
      }
      else
      {
         std::cerr << g_config.GetProgName ()
                   << ": WARNING: Section \""
                   << ossLine.str () << '/' << IX1A_CFG_SECT_LINE_ACCESS
                   << "\" does not exist"
                   << std::endl;
      }
      
      // fill in the D-channel parameters
      pLineParams->dchan.dwSize = htole32 (sizeof (pLineParams->dchan));
      if (iterLineAcc != cfgFile.SectionEnd ())
      {
         pLineParams->dchan.dwDProtocol =
            htole32 (iterLineAcc->GetKeyValueAsUnsignedNumber<u_int32_t>
                        (IX1A_CFG_KEY_DCHAN_PROTOCOL));
         pLineParams->dchan.dwSignalingMode = 0;
         pLineParams->dchan.dwTeiType =
            htole32 (iterLineAcc->GetKeyValueAsUnsignedNumber<u_int32_t>
                        (IX1A_CFG_KEY_TEI_TYPE));
         pLineParams->dchan.dwTeiValue =
            htole32 (iterLineAcc->GetKeyValueAsUnsignedNumber<u_int32_t>
                        (IX1A_CFG_KEY_TEI_VALUE));
      }
      
      // fill in the service profile parameters (not for Primary boards)
      pLineParams->serviceProfile.dwSize =
         htole32 (sizeof (pLineParams->serviceProfile));
      if (! fPrimary)
      {
         if (iterSpid != cfgFile.SectionEnd ())
         {
            pLineParams->serviceProfile.dwSignalDN2AsPrimaryDN1 =
               htole32 (iterSpid->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_SIGNAL_DN2_AS_DN1));
            pLineParams->serviceProfile.dwSignalAddDN1AsPrimaryDN1 =
               htole32 (iterSpid->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_SIGNAL_ADD_DN1_AS_DN1));
            pLineParams->serviceProfile.dwSignalAddDN2AsPrimaryDN2 =
               htole32 (iterSpid->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_SIGNAL_ADD_DN2_AS_DN2));
            pLineParams->serviceProfile.dwSpid1DataOn =
               htole32 (iterSpid->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_SPID1_DATA_ON));
            pLineParams->serviceProfile.dwSpid2DataOn =
               htole32 (iterSpid->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_SPID2_DATA_ON));
            pLineParams->serviceProfile.dwSpid1VoiceOn =
               htole32 (iterSpid->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_SPID1_VOICE_ON));
            pLineParams->serviceProfile.dwSpid2VoiceOn =
               htole32 (iterSpid->GetKeyValueAsUnsignedNumber<u_int32_t>
                           (IX1A_CFG_KEY_SPID2_VOICE_ON));
            pLineParams->serviceProfile.dwMaxDnCount = IX1A_DN_COUNT;
            strlcpy ((char *) pLineParams->serviceProfile.szSpid1,
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_SPID1).c_str (),
                     sizeof (pLineParams->serviceProfile.szSpid1));
            strlcpy ((char *) pLineParams->serviceProfile.szSpid2,
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_SPID2).c_str (),
                     sizeof (pLineParams->serviceProfile.szSpid2));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn1 [0],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN1).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn1 [0]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn1 [1],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN1_ADD1).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn1 [1]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn1 [2],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN1_ADD2).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn1 [2]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn1 [3],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN1_ADD3).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn1 [3]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn1 [4],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN1_ADD4).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn1 [4]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn2 [0],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN2).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn2 [0]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn2 [1],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN2_ADD1).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn2 [1]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn2 [2],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN2_ADD2).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn2 [2]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn2 [3],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN2_ADD3).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn2 [3]));
            strlcpy ((char *) pLineParams->serviceProfile.aszDn2 [4],
                     iterSpid->GetKeyValueAsString
                        (IX1A_CFG_KEY_DN2_ADD4).c_str (),
                     sizeof (pLineParams->serviceProfile.aszDn2 [4]));
         }
         else
         {
            std::cerr << g_config.GetProgName ()
                      << ": WARNING: Section \""
                      << ossLine.str () << '/' << IX1A_CFG_SECT_SPID_SETTINGS
                      << "\" does not exist"
                      << std::endl;
         }
      }
      
      // fill in the SAS parameters (not supported, leave set to null)
      pLineParams->sas.dwSize = htole32 (sizeof (pLineParams->sas));
   }
   
   return (true);
} // 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 */
