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

// Local includes
#include "config.h"
#include "ctlrlist.h"
#include "cfgfile.h"
#include "avmaic_board_params.h"
#include "avmaic_cfgdefs.h"





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





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



/// Numerical values for the D-channel protocols available for AVM active ISDN
/// controllers.
enum DProtocol_t
{
   DP_ERROR     = -1,
   DP_NONE      = 0,
   DP_DSS1      = 1,
   DP_D64S      = 2,
   DP_D64S2     = 3,
   DP_D64SD     = 4,
   DP_DS01      = 5,
   DP_DS02      = 6,
   DP_CT1       = 7,
   DP_VN3       = 8,
   DP_AUSTEL    = 9,
   DP_5ESS      = 10, // need SPID1, SPID2, DN1, DN2
   DP_NI1       = 11, // need SPID1, SPID2, DN1, DN2
   DP_DSS1MOBIL = 12,
   DP_1TR6MOBIL = 13,
   DP_GSM       = 14,
   DP_1TR6      = 15,
   DP_T1        = 16
};

/// Mapping entry for a D-channel protocol name and its numeric value.
struct DProtocolMap_t
{
   const char  *pszName;
   DProtocol_t  dprot;
};

/// The mapping between the numeric values and the D-channel protocol names.
DProtocolMap_t g_dprotMap [] =
   {
      { "DSS1MOBIL", DP_DSS1MOBIL },
      { "1TR6MOBIL", DP_1TR6MOBIL },
      { "DSS1",      DP_DSS1      },
      { "D64S",      DP_D64S      },
      { "D64S2",     DP_D64S2     },
      { "D64SD",     DP_D64SD     },
      { "DS01",      DP_DS01      },
      { "DS02",      DP_DS02      },
      { "CT1",       DP_CT1       },
      { "VN3",       DP_VN3       },
      { "AUSTEL",    DP_AUSTEL    },
      { "5ESS",      DP_5ESS      },
      { "NI1",       DP_NI1       },
      { "GSM",       DP_GSM       },
      { "1TR6",      DP_1TR6      },
      { "T1",        DP_T1        },
      { NULL,        DP_NONE      }
   };






// === 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 AVM
 * 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.
 *
 * @param fIsT1                 I: The board shall be a T1 or not.
 * @param nNumPorts             I: The number of ISDN ports of the board.
 * @param paBoardData           O: The board data records to be filled. At
 *                                 least nNumPorts entries must be provided.
 *
 * @return Nothing.
 */
static void SetBoardDataDefaults
   (bool                     fIsT1,
    size_t                   nNumPorts,
    AvmaicBoardParameters_t *paBoardData);

/**
 * Add entries for a board data structure to a configuration database.
 *
 * @param strBoardName          I: The board name for its base section.
 * @param strFirmwareFileName   I: The file name for the firmware file,
 *                                 relative to the base directory in the global
 *                                 section of the configuration database.
 * @param nNumPorts             I: The number of ports for the board.
 * @param boardData             I: The board data record(s) to write. At least
 *                                 nNumPorts entries must be provided.
 * @param cfgFile               I/O: The configuration database to modify.
 *
 * @return Nothing.
 */
static void AddBoardConfigToConfigDb
   (const std::string             &strBoardName,
    const std::string             &strFirmwareFileName,
    size_t                         nNumPorts,
    const AvmaicBoardParameters_t *paBoardData,
    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 strConfigData         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,
    std::string                  &strConfigData);

/**
 * Read line access settings from a configuration file.
 *
 * @param nNumPorts             I: The number of ports to get settings for.
 * @param paBoardData           O: The array of board data records to fill. At
 *                                 least nNumPorts entries must be available.
 * @param cfgBoardSect          I: The base configuration section the port
 *                                 settings are below.
 * @param cfgFile               I: The configuration database to read from.
 *
 * @retval true                 The board data record(s) is(are) filled
 *                              successfully.
 * @retval false                Failure.
 */
static bool ReadConfigData
   (size_t                       nNumPorts,
    AvmaicBoardParameters_t     *paBoardData,
    const CConfigFile::CSection &cfgBoardSect,
    CConfigFile                 &cfgFile);

/**
 * 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);

/**
 * Translate a D-channel protocol number into a string.
 *
 * @param dprot                 I: The D-channel protocol number to translate.
 *
 * @return A string for the D-channel protocol number. If the numeric argument
 *         is invalid, an empty string is returned.
 */
static const char *GetDProtocolName
   (DProtocol_t dprot);

/**
 * Translate a D-channel protocol name into a number.
 *
 * @param str                   I: The string to translate.
 *
 * @retval DP_ERROR             An invalid string was recognised.
 * @retval DP_NONE              No D-channel protocol specified, i.e. an empty
 *                              string was given.
 * @retval Else                 The numerical D-channel protocol.
 */
static DProtocol_t GetDProtocolFromString
   (const std::string &str);

/**
 * Add a patch value to a board configuration data string.
 *
 * @param strConfigData         I/O: The configuration data string to append a
 *                                 new setting to.
 * @param strKey                I: The key string for the new value.
 * @param strVal                I: The value string to add (normally of length
 *                                 1).
 *
 * @return Nothing.
 */
static void AddPatchValue
   (std::string       &strConfigData,
    const std::string &strKey,
    const std::string &strVal);

/**
 * Add a numeric patch value (byte) to a board configuration data string.
 *
 * @param strConfigData         I/O: The configuration data string to append a
 *                                 new setting to.
 * @param strKey                I: The key string for the new value.
 * @param bVal                  I: The byte value to add.
 *
 * @return Nothing.
 */
static void AddPatchByte
   (std::string       &strConfigData,
    const std::string &strKey,
    u_int8_t           bVal);





// === 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 AVM
 * 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 AVM 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 AVM active ISDN boards..."
                << std::endl;
   }
   
   // create an empty configuration database
   CConfigFile cfgFile;
   
   // create list of available AVM controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cout << "No AVM active ISDN controllers found, no configuration file created"
                << std::endl;
      return (2);
   }
   if (! g_config.Quiet ())
   {
      std::cout << "Found " << ctlrList.size ()
                << " AVM 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 AVM active ISDN boards..."
                << std::endl;
   }
   
   // read in the existing configuration database
   CConfigFile cfgFile (g_config.GetConfigFileName ());
   
   // Create list of available AVM controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No AVM active ISDN controllers found, will not perform update"
                << std::endl;
      return (2);
   }
   if (! g_config.Quiet ())
   {
      std::cout << "Found " << ctlrList.size ()
                << " AVM 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 ("/");
           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 ("/");
         }
         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 ("/") + 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)
{
   // prepare the firmware directory for later use; be sure it ends with a
   // path separator
   std::string strBaseDir;
   strBaseDir = g_config.GetBaseDirectory ();
   if (strBaseDir.length () > 0 &&
       strBaseDir [strBaseDir.length () - 1] != '/')
   {
      strBaseDir += '/';
   }
   
   // determine the board type according to the controller name and modify the
   // necessary settings
   std::string strFirmwareFileName;
   size_t      nNumPorts;
   bool        fIsT1;
   if (ctlr.GetBoardName ().find ("B1") != std::string::npos)
   {
      strFirmwareFileName  = strBaseDir + "b1.t4";
      nNumPorts            = 1;
      fIsT1                = false;
   }
   else if (ctlr.GetBoardName ().find ("C2") != std::string::npos)
   {
      strFirmwareFileName  = strBaseDir + "c2.bin";
      nNumPorts            = 2;
      fIsT1                = false;
   }
   else if (ctlr.GetBoardName ().find ("C4") != std::string::npos)
   {
      strFirmwareFileName  = strBaseDir + "c4.bin";
      nNumPorts            = 4;
      fIsT1                = false;
   }
   else if (ctlr.GetBoardName ().find ("T1") != std::string::npos)
   {
      strFirmwareFileName  = strBaseDir + "t1.t4";
      nNumPorts            = 1;
      fIsT1                = true;
   }
   else
   {
      std::ostringstream oss;
      oss << "Unable to detect board type for board \""
          << ctlr.GetBoardName ()
          << "\", cannot provide board configuration";
      throw std::runtime_error (oss.str ());
   }
   
   // initialise the board configuration structure(s) with default values
   AvmaicBoardParameters_t aBoardData [4];
   SetBoardDataDefaults (fIsT1, nNumPorts, aBoardData);
   
   // finally add sections and values to the configuration database for the
   // board
   AddBoardConfigToConfigDb
      (ctlr.GetBoardName (), strFirmwareFileName,
       nNumPorts, aBoardData, cfgFile);
   
} // AddControllerToConfigDb





/**
 * Initialise a board data structure with defaults.
 *
 * @param fIsT1                 I: The board shall be a T1 or not.
 * @param nNumPorts             I: The number of ISDN ports of the board.
 * @param paBoardData           O: The board data records to be filled. At
 *                                 least nNumPorts entries must be provided.
 *
 * @return Nothing.
 */

static void SetBoardDataDefaults
   (bool                     fIsT1,
    size_t                   nNumPorts,
    AvmaicBoardParameters_t *paBoardData)
{
   size_t n;
   
   for (n = 0; n < nNumPorts; ++n)
   {
      memset (&(paBoardData [n]), 0, sizeof (paBoardData [n]));
      
      strncpy (paBoardData [n].szDProtocol, GetDProtocolName (DP_DSS1),
               sizeof (paBoardData [n].szDProtocol));
      if (fIsT1)
      {
         paBoardData [n].fP2P      = 1;
         paBoardData [n].uTeiValue = 0;
      }
      else
      {
         paBoardData [n].fP2P = 0;
      }
   }

} // SetBoardDataDefaults





/**
 * Add entries for a board data structure to a configuration database.
 *
 * @param strBoardName          I: The board name for its base section.
 * @param strFirmwareFileName   I: The file name for the firmware file,
 *                                 relative to the base directory in the global
 *                                 section of the configuration database.
 * @param nNumPorts             I: The number of ports for the board.
 * @param boardData             I: The board data record(s) to write. At least
 *                                 nNumPorts entries must be provided.
 * @param cfgFile               I/O: The configuration database to modify.
 *
 * @return Nothing.
 */

static void AddBoardConfigToConfigDb
   (const std::string             &strBoardName,
    const std::string             &strFirmwareFileName,
    size_t                         nNumPorts,
    const AvmaicBoardParameters_t *paBoardData,
    CConfigFile                   &cfgFile)
{
   // create the board section and add the download file names
   CConfigFile::CSectionIterator iterBoard;
   std::string                   strSect;
   strSect = std::string ("/") + strBoardName;
   iterBoard = cfgFile.SectionAdd (strSect);
   iterBoard->AddKeyValuePair (AVMAIC_CFG_KEY_FIRMWARE_FILE,
                               strFirmwareFileName,
                               AVMAIC_COMMENT_KEY_FIRMWARE_FILE);

   // for every ISDN port create a line port section
   CConfigFile::CSectionIterator iterPort;
   size_t n;
   for (n = 0; n < nNumPorts; ++n)
   {
      std::ostringstream oss;
      oss << iterBoard->GetFullName () << '/' << AVMAIC_CFG_SECT_PORT
          << n + 1;
      iterPort = cfgFile.SectionAdd (oss.str ());
      
      iterPort->AddKeyValuePair (AVMAIC_CFG_KEY_DPROTOCOL,
                                 paBoardData [n].szDProtocol,
                                 AVMAIC_COMMENT_KEY_DPROTOCOL);
      iterPort->AddKeyValuePair (AVMAIC_CFG_KEY_P2P,
                                 paBoardData [n].fP2P,
                                 AVMAIC_COMMENT_KEY_P2P);
      iterPort->AddKeyValuePair (AVMAIC_CFG_KEY_TEI_VALUE,
                                 paBoardData [n].uTeiValue,
                                 AVMAIC_COMMENT_KEY_TEI_VALUE);
      if (strBoardName.find ("T1") == std::string::npos)
      {
         iterPort->AddKeyValuePair (AVMAIC_CFG_KEY_SPID1,
                                    paBoardData [n].szSpid1,
                                    AVMAIC_COMMENT_KEY_SPID1);
         iterPort->AddKeyValuePair (AVMAIC_CFG_KEY_DN1,
                                    paBoardData [n].szDn1,
                                    AVMAIC_COMMENT_KEY_DN1);
         iterPort->AddKeyValuePair (AVMAIC_CFG_KEY_SPID2,
                                    paBoardData [n].szSpid2,
                                    AVMAIC_COMMENT_KEY_SPID2);
         iterPort->AddKeyValuePair (AVMAIC_CFG_KEY_DN2,
                                    paBoardData [n].szDn2,
                                    AVMAIC_COMMENT_KEY_DN2);
      }
   }

} // 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 AVM controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No AVM active ISDN controllers found, unable to perform download"
                << std::endl;
      return (2);
   }
   
   // distinguish between downloading only one board or all AVM 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 AVM 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 AVM 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 ("/") + 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 AVM 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 AVM 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 ("/") + 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 AVM active ISDN boards ("
                   << ctlrList.size () << ")..."
                   << std::endl;
      }
      
      // first check if the configuration database contains non-existing boards
      CConfigFile::CSectionIterator iterSect;
      for (iterSect = cfgFile.SectionBegin ("/");
           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 AVM 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 ("/") + 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 AVM controllers
   CControllerList ctlrList (g_config.GetDriverName (), g_config.Quiet ());
   if (ctlrList.size () == 0)
   {
      std::cerr << g_config.GetProgName ()
                << ": No AVM active ISDN controllers found, unable to perform reset"
                << std::endl;
      return (2);
   }
   
   // distinguish between resetting only one board or all AVM 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 AVM 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 AVM 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 ("/") + 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 AVM 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 AVM 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 ("/") + 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 AVM active ISDN boards ("
                   << ctlrList.size () << ")..."
                   << std::endl;
      }
      
      // first check if the configuration database contains non-existing boards
      CConfigFile::CSectionIterator iterSect;
      for (iterSect = cfgFile.SectionBegin ("/");
           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 AVM 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 ("/") + 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                   strFirmwareFileName;
   int                           fdFirmware;
   struct stat                   statbuf;
   size_t                        nLenFirmware;
   std::string                   strConfigData;
   CAPICtlrDataBlock_t           aCtlrDataBlocks [2];
   size_t                        nNumBlocks;
   unsigned                      uRes;
   
   // open the file for the firmware code
   strFirmwareFileName = cfgSect.GetKeyValueAsString
                            (AVMAIC_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;
      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);
      return (2);
   }
   nLenFirmware = (size_t) (statbuf.st_size);
   
   // prepare the first download block for the firmware code
   bzero (aCtlrDataBlocks, sizeof (aCtlrDataBlocks));
   aCtlrDataBlocks [0].nLenDataBlock = nLenFirmware;
   aCtlrDataBlocks [0].paucDataBlock =
      (unsigned char *)
         mmap (NULL, nLenFirmware, PROT_READ, MAP_PRIVATE, fdFirmware, 0);
   if (aCtlrDataBlocks [0].paucDataBlock == MAP_FAILED)
   {
      std::cerr << g_config.GetProgName ()
                << ": Error mapping firmware file content (\""
                << strFirmwareFileName << "\", " << nLenFirmware
                << " bytes) into memory: "
                << strerror (errno) << " (" << errno << ")" << std::endl;
      (void) close (fdFirmware);
      return (2);
   }
   nNumBlocks = 1;
   
   // create a data block for the board configuration
   if (CreateConfigData (cfgSect, cfgFile, strConfigData) &&
       strConfigData.length () != 0)
   {
      aCtlrDataBlocks [1].nLenDataBlock = le32toh (strConfigData.length ());
      aCtlrDataBlocks [1].paucDataBlock =
         (unsigned char *) (strConfigData.data ());
      ++nNumBlocks;
   }
   
   // now finally perform the download request
   uRes = capi20_reset_ctlr (ctlrData.GetControllerNumber (),
                             nNumBlocks, aCtlrDataBlocks);
   (void) munmap (aCtlrDataBlocks [0].paucDataBlock, 
                  aCtlrDataBlocks [0].nLenDataBlock);
   (void) close (fdFirmware);
   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 strConfigData         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,
    std::string                 &strConfigData)
{
   // initialise the config data string to an empty string
   strConfigData.erase ();
   
   // determine the number of ports for the board
   size_t nNumPorts;
   if (cfgSect.GetShortName ().find ("C4") != std::string::npos)
   {
      nNumPorts = 4;
   }
   else if (cfgSect.GetShortName ().find ("C2") != std::string::npos)
   {
      nNumPorts = 2;
   }
   else
   {
      nNumPorts = 1;
   }
   
   // get configuration data for all ports from the configuration file
   AvmaicBoardParameters_t aBoardData [4];
   memset (aBoardData, 0, sizeof (aBoardData));
   if (! ReadConfigData (nNumPorts, aBoardData, cfgSect, cfgFile))
   {
      return (false);
   }
   
   // now finally add settings to the board configuration string
   DProtocol_t dprot;
   size_t      n;
   for (n = 0; n < nNumPorts; ++n)
   {
      if (nNumPorts != 1)
      {
         AddPatchByte (strConfigData, "CtlrNr", (unsigned char) n);
      }
      
      dprot = GetDProtocolFromString (aBoardData [n].szDProtocol);
      if (dprot != DP_T1)
      {
         AddPatchValue (strConfigData, "WATCHDOG", std::string ("1"));
         AddPatchByte (strConfigData, "AutoFrame", 1);
      }
      else
      {
         AddPatchValue (strConfigData, "WATCHDOG", std::string ("0"));
      }
      switch (dprot)
      {
         case DP_DSS1:
         case DP_T1:
         case DP_GSM:
         case DP_1TR6:
         case DP_NONE:
            break;
   
         case DP_D64S:
         case DP_D64S2:
         case DP_D64SD:
            aBoardData [n].fP2P = 0;
            AddPatchValue (strConfigData, "FV2", std::string ("2"));
            AddPatchByte (strConfigData, "TEI", 0);
            break;
   
         case DP_DS01:
         case DP_DS02:
            aBoardData [n].fP2P = 0;
            AddPatchValue (strConfigData, "FV2", std::string ("1"));
            AddPatchByte (strConfigData, "TEI", 0);
            break;
   
         case DP_CT1:
            AddPatchByte (strConfigData, "PROTOCOL", 1);
            break;
   
         case DP_VN3:
            AddPatchByte (strConfigData, "PROTOCOL", 2);
            break;
   
         case DP_AUSTEL:
            AddPatchByte (strConfigData, "PROTOCOL", 4);
            break;
   
         case DP_NI1:
            aBoardData [n].fP2P = 0;
            AddPatchByte (strConfigData, "PROTOCOL", 3);
            break;
   
         case DP_5ESS:
            aBoardData [n].fP2P = 0;
            AddPatchByte (strConfigData, "PROTOCOL", 5);
            break;
   
         case DP_DSS1MOBIL:
            AddPatchValue (strConfigData, "PatchMobileMode", std::string ("0"));
            break;
   
         case DP_1TR6MOBIL:
            AddPatchValue (strConfigData, "PatchMobileMode", std::string ("0"));
            break;
   
         default:
            std::cerr << g_config.GetProgName ()
                      << ": WARNING: Invalid D-channel protocol \""
                      << aBoardData [n].szDProtocol
                      << "\" in board configuration for \""
                      << cfgSect.GetShortName () << "\", port " << n + 1
                      << std::endl;
            return (false);
      }
   
      // D-channel protocols NI1 and 5ESS also need SPID settings
      if (dprot == DP_NI1 || dprot == DP_5ESS)
      {
         if (aBoardData [n].szSpid1 [0] != '\0' &&
             aBoardData [n].szDn1 [0] != '\0')
         {
            AddPatchValue (strConfigData, "DN", aBoardData [n].szDn1);
            AddPatchValue (strConfigData, "SPID", aBoardData [n].szSpid1);
         }
         if (aBoardData [n].szSpid2 [0] != '\0' &&
             aBoardData [n].szDn2 [0] != '\0')
         {
            AddPatchValue (strConfigData, "DN2", aBoardData [n].szDn2);
            AddPatchValue (strConfigData, "SPID2", aBoardData [n].szSpid2);
         }
      }
   
      // for Point-to-Point operation we must change the default behaviour and
      // specify the TEI value
      if (aBoardData [n].fP2P)
      {
         AddPatchByte (strConfigData, "P2P", 1);
         AddPatchByte (strConfigData, "TEI",
                       (u_int8_t) (aBoardData [n].uTeiValue));
      }
   }

   // the configuration data block must be closed with an additional null byte
   if (strConfigData.length () > 0)
   {
      strConfigData += '\0';
   }
   
   return (true);
} // CreateConfigData





/**
 * Read line access settings from a configuration file.
 *
 * @param nNumPorts             I: The number of ports to get settings for.
 * @param paBoardData           O: The array of board data records to fill. At
 *                                 least nNumPorts entries must be available.
 * @param cfgBoardSect          I: The base configuration section the port
 *                                 settings are below.
 * @param cfgFile               I: The configuration database to read from.
 *
 * @retval true                 The board data record(s) is(are) filled
 *                              successfully.
 * @retval false                Failure.
 */

static bool ReadConfigData
   (size_t                       nNumPorts,
    AvmaicBoardParameters_t     *paBoardData,
    const CConfigFile::CSection &cfgBoardSect,
    CConfigFile                 &cfgFile)
{
   CConfigFile::CSectionIterator iterPortSect;
   std::string                   strDProtocol;
   size_t                        n;
   
   for (n = 0; n < nNumPorts; ++n)
   {
      std::ostringstream oss;
      oss << cfgBoardSect.GetFullName () << '/' << AVMAIC_CFG_SECT_PORT
          << n + 1;
      iterPortSect = cfgFile.SectionFind (oss.str ());
      if (iterPortSect == cfgFile.SectionEnd ())
      {
         std::cerr << g_config.GetProgName ()
                   << ": WARNING: Line access setting section \"" << oss.str ()
                   << "\" is missing in configuration for board \""
                   << cfgBoardSect.GetShortName () << "\""
                   << std::endl;
         return (false);
      }
   
      strlcpy (paBoardData [n].szDProtocol,
               iterPortSect->GetKeyValueAsString
                  (AVMAIC_CFG_KEY_DPROTOCOL).c_str (),
               sizeof (paBoardData [n].szDProtocol));
      
      paBoardData [n].fP2P =
         (iterPortSect->GetKeyValueAsSignedNumber<int>
             (AVMAIC_CFG_KEY_P2P) != 0);
      paBoardData [n].uTeiValue =
         iterPortSect->GetKeyValueAsUnsignedNumber<unsigned>
            (AVMAIC_CFG_KEY_TEI_VALUE);
   
      strlcpy (paBoardData [n].szSpid1,
               iterPortSect->GetKeyValueAsString
                  (AVMAIC_CFG_KEY_SPID1).c_str (),
               sizeof (paBoardData [n].szSpid1));
      strlcpy (paBoardData [n].szDn1,
               iterPortSect->GetKeyValueAsString
                  (AVMAIC_CFG_KEY_DN1).c_str (),
               sizeof (paBoardData [n].szDn1));
      strlcpy (paBoardData [n].szSpid2,
               iterPortSect->GetKeyValueAsString
                  (AVMAIC_CFG_KEY_SPID2).c_str (),
               sizeof (paBoardData [n].szSpid2));
      strlcpy (paBoardData [n].szDn2,
               iterPortSect->GetKeyValueAsString
                  (AVMAIC_CFG_KEY_DN2).c_str (),
               sizeof (paBoardData [n].szDn2));
   }
         
   return (true);
} // ReadConfigData





/**
 * 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 */





/**
 * Translate a D-channel protocol number into a string.
 *
 * @param dprot                 I: The D-channel protocol number to translate.
 *
 * @return A string for the D-channel protocol number. If the numeric argument
 *         is invalid, an empty string is returned.
 */

static const char *GetDProtocolName
   (DProtocol_t dprot)
{
   if (dprot == DP_NONE)
   {
      return ("");
   }
   if (dprot == DP_ERROR)
   {
      return ("ERROR");
   }
   
   struct DProtocolMap_t *p;
   for (p = g_dprotMap; p->pszName != NULL; ++p)
   {
      if (p->dprot == dprot)
      {
         return (p->pszName);
      }
   }

   return ("ERROR");
} // GetDProtocolName





/**
 * Translate a D-channel protocol name into a number.
 *
 * @param str                   I: The string to translate.
 *
 * @retval DP_ERROR             An invalid string was recognised.
 * @retval DP_NONE              No D-channel protocol specified, i.e. an empty
 *                              string was given.
 * @retval Else                 The numerical D-channel protocol.
 */

static DProtocol_t GetDProtocolFromString
   (const std::string &str)
{
   if (str.length () == 0)
   {
      return (DP_NONE);
   }
   
   struct DProtocolMap_t *p;
   for (p = g_dprotMap; p->pszName != NULL; ++p)
   {
      if (strcasecmp (str.c_str (), p->pszName) == 0)
      {
         return (p->dprot);
      }
   }

   return (DP_ERROR);
} // GetDProtocolFromString





/**
 * Add a patch value to a board configuration data string.
 *
 * @param strConfigData         I/O: The configuration data string to append a
 *                                 new setting to.
 * @param strKey                I: The key string for the new value.
 * @param strVal                I: The value string to add (normally of length
 *                                 1).
 *
 * @return Nothing.
 */

static void AddPatchValue
   (std::string       &strConfigData,
    const std::string &strKey,
    const std::string &strVal)
{
   strConfigData += std::string (":") + strKey + '\0';
   strConfigData += strVal + '\0';

} // AddPatchValue





/**
 * Add a numeric patch value (byte) to a board configuration data string.
 *
 * @param strConfigData         I/O: The configuration data string to append a
 *                                 new setting to.
 * @param strKey                I: The key string for the new value.
 * @param bVal                  I: The byte value to add.
 *
 * @return Nothing.
 */

static void AddPatchByte
   (std::string       &strConfigData,
    const std::string &strKey,
    u_int8_t           bVal)
{
   std::ostringstream oss;
   oss << "\\x"
       << std::hex << std::setfill ('0') << std::setw (2)
       << (unsigned) bVal;
   AddPatchValue (strConfigData, strKey, oss.str ());
   
} // AddPatchByte
