Home | History | Annotate | Line # | Download | only in common
hwcfuncs.c revision 1.1.1.1
      1 /* Copyright (C) 2021 Free Software Foundation, Inc.
      2    Contributed by Oracle.
      3 
      4    This file is part of GNU Binutils.
      5 
      6    This program is free software; you can redistribute it and/or modify
      7    it under the terms of the GNU General Public License as published by
      8    the Free Software Foundation; either version 3, or (at your option)
      9    any later version.
     10 
     11    This program is distributed in the hope that it will be useful,
     12    but WITHOUT ANY WARRANTY; without even the implied warranty of
     13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14    GNU General Public License for more details.
     15 
     16    You should have received a copy of the GNU General Public License
     17    along with this program; if not, write to the Free Software
     18    Foundation, 51 Franklin Street - Fifth Floor, Boston,
     19    MA 02110-1301, USA.  */
     20 
     21 /* Hardware counter profiling */
     22 #include "hwcdrv.h"
     23 #include "hwcfuncs.h"
     24 
     25 /*---------------------------------------------------------------------------*/
     26 /* macros */
     27 
     28 #define IS_GLOBAL           /* Mark global symbols */
     29 #define HWCDRV_API static   /* Mark functions used by hwcdrv API */
     30 
     31 /*---------------------------------------------------------------------------*/
     32 /* static variables */
     33 static uint_t cpcN_npics;
     34 static char hwcfuncs_errmsg_buf[1024];
     35 static int hwcfuncs_errmsg_enabled = 1;
     36 static int hwcfuncs_errmsg_valid;
     37 
     38 /* --- user counter selections and options */
     39 static unsigned hwcdef_cnt; /* number of *active* hardware counters */
     40 static Hwcentry hwcdef[MAX_PICS]; /* HWC definitions */
     41 static Hwcentry *hwctable[MAX_PICS]; /* HWC definitions */
     42 
     43 /* --- drivers --- */
     44 
     45 // default driver
     46 
     47 HWCDRV_API int
     48 hwcdrv_init (hwcfuncs_abort_fn_t abort_ftn, int* tsd_sz)
     49 {
     50   return -1;
     51 }
     52 
     53 HWCDRV_API void
     54 hwcdrv_get_info (
     55 		 int * cpuver, const char ** cciname,
     56 		 uint_t * npics, const char ** docref, uint64_t* support) { }
     57 
     58 HWCDRV_API int
     59 hwcdrv_enable_mt (hwcfuncs_tsd_get_fn_t tsd_ftn)
     60 {
     61   return -1;
     62 }
     63 
     64 HWCDRV_API int
     65 hwcdrv_get_descriptions (hwcf_hwc_cb_t *hwc_find_action,
     66 			 hwcf_attr_cb_t *attr_find_action)
     67 {
     68   return 0;
     69 }
     70 
     71 HWCDRV_API int
     72 hwcdrv_assign_regnos (Hwcentry *entries[], unsigned numctrs)
     73 {
     74   return -1;
     75 }
     76 
     77 HWCDRV_API int
     78 hwcdrv_create_counters (unsigned hwcdef_cnt, Hwcentry *hwcdef)
     79 {
     80   return -1;
     81 }
     82 
     83 HWCDRV_API int
     84 hwcdrv_read_events (hwc_event_t *events, hwc_event_samples_t*samples)
     85 {
     86   return -1;
     87 }
     88 
     89 HWCDRV_API int
     90 hwcdrv_start (void)
     91 {
     92   return -1;
     93 }
     94 
     95 HWCDRV_API int
     96 hwcdrv_overflow (siginfo_t *si, hwc_event_t *s, hwc_event_t *t)
     97 {
     98   return 0;
     99 }
    100 
    101 HWCDRV_API int
    102 hwcdrv_sighlr_restart (const hwc_event_t *sample)
    103 {
    104   return -1;
    105 }
    106 
    107 HWCDRV_API int
    108 hwcdrv_lwp_suspend (void)
    109 {
    110   return -1;
    111 }
    112 
    113 HWCDRV_API int
    114 hwcdrv_lwp_resume (void)
    115 {
    116   return -1;
    117 }
    118 
    119 HWCDRV_API int
    120 hwcdrv_free_counters (void)
    121 {
    122   return 0;
    123 }
    124 
    125 HWCDRV_API int
    126 hwcdrv_lwp_init (void)
    127 {
    128   return 0;
    129 }
    130 
    131 HWCDRV_API void
    132 hwcdrv_lwp_fini (void) { }
    133 
    134 static hwcdrv_api_t hwcdrv_default = {
    135   hwcdrv_init,
    136   hwcdrv_get_info,
    137   hwcdrv_enable_mt,
    138   hwcdrv_get_descriptions,
    139   hwcdrv_assign_regnos,
    140   hwcdrv_create_counters,
    141   hwcdrv_start,
    142   hwcdrv_overflow,
    143   hwcdrv_read_events,
    144   hwcdrv_sighlr_restart,
    145   hwcdrv_lwp_suspend,
    146   hwcdrv_lwp_resume,
    147   hwcdrv_free_counters,
    148   hwcdrv_lwp_init,
    149   hwcdrv_lwp_fini,
    150   -1                        // hwcdrv_init_status
    151 };
    152 
    153 static hwcdrv_api_t *hwcdrv_driver = &hwcdrv_default;
    154 
    155 
    156 /*---------------------------------------------------------------------------*/
    157 /* misc */
    158 
    159 /* print a counter definition (for debugging) */
    160 static void
    161 ctrdefprint (int dbg_lvl, const char * hdr, Hwcentry*phwcdef)
    162 {
    163   TprintfT (dbg_lvl, "%s: name='%s', int_name='%s',"
    164 	    " reg_num=%d, timecvt=%d, memop=%d, "
    165 	    "interval=%d, tag=%u, reg_list=%p\n",
    166 	    hdr, phwcdef->name, phwcdef->int_name, phwcdef->reg_num,
    167 	    phwcdef->timecvt, phwcdef->memop, phwcdef->val,
    168 	    phwcdef->sort_order, phwcdef->reg_list);
    169 }
    170 
    171 /*---------------------------------------------------------------------------*/
    172 /* errmsg buffering */
    173 
    174 /* errmsg buffering is needed only because the most descriptive error
    175    messages from CPC are delivered using a callback mechanism.
    176    hwcfuncs_errmsg_get() should only be used during initialization, and
    177    ideally,  only to provide feedback to an end user when his counters can't
    178    be bound to HW.
    179  */
    180 IS_GLOBAL char *
    181 hwcfuncs_errmsg_get (char *buf, size_t bufsize, int enable)
    182 {
    183   hwcfuncs_errmsg_enabled = 0;
    184   if (buf && bufsize)
    185     {
    186       if (hwcfuncs_errmsg_valid)
    187 	{
    188 	  strncpy (buf, hwcfuncs_errmsg_buf, bufsize);
    189 	  buf[bufsize - 1] = 0;
    190 	}
    191       else
    192 	*buf = 0;
    193     }
    194   hwcfuncs_errmsg_buf[0] = 0;
    195   hwcfuncs_errmsg_valid = 0;
    196   hwcfuncs_errmsg_enabled = enable;
    197   return buf;
    198 }
    199 
    200 /* used by cpc to log an error */
    201 IS_GLOBAL void
    202 hwcfuncs_int_capture_errmsg (const char *fn, int subcode,
    203 			     const char *fmt, va_list ap)
    204 {
    205   if (hwcfuncs_errmsg_enabled &&
    206       !hwcfuncs_errmsg_valid)
    207     {
    208       vsnprintf (hwcfuncs_errmsg_buf, sizeof (hwcfuncs_errmsg_buf), fmt, ap);
    209       TprintfT (DBG_LT0, "hwcfuncs: cpcN_capture_errmsg(): %s\n",
    210 		hwcfuncs_errmsg_buf);
    211       hwcfuncs_errmsg_valid = 1;
    212     }
    213   return;
    214 }
    215 
    216 /* Log an internal error to the CPC error buffer.
    217  * Note: only call this during init functions.
    218  * Note: when most cpc calls fail, they will call cpcN_capture_errmsg()
    219  *   directly, so only call logerr() when a non-cpc function fails.
    220  */
    221 IS_GLOBAL void
    222 hwcfuncs_int_logerr (const char *format, ...)
    223 {
    224   va_list va;
    225   va_start (va, format);
    226   hwcfuncs_int_capture_errmsg ("logerr", 0, format, va);
    227   va_end (va);
    228 }
    229 
    230 /* utils to parse counter strings */
    231 static void
    232 clear_hwcdefs ()
    233 {
    234   for (unsigned idx = 0; idx < MAX_PICS; idx++)
    235     {
    236       static Hwcentry empty;
    237       hwcdef[idx] = empty; // leaks strings and reg_list array
    238       hwcdef[idx].reg_num = REGNO_ANY;
    239       hwcdef[idx].val = -1;
    240       hwcdef[idx].sort_order = -1;
    241     }
    242 }
    243 
    244 /* initialize hwcdef[] based on user's counter definitions */
    245 static int
    246 process_data_descriptor (const char *defstring)
    247 {
    248   /*
    249    * <defstring> format should be of format
    250    *  :%s:%s:0x%x:%d:%lld:%d:%d:0x%x[,%s...repeat for each ctr]
    251    * where the counter fields are:
    252    *  :<userName>:<internalCtr>:<register>:<timeoutVal>[:m<min_time>]:<tag>:<timecvt>:<memop>
    253    * See Coll_Ctrl::build_data_desc().
    254    */
    255   int err = 0;
    256   char *ds = NULL;
    257   char *dsp = NULL;
    258   unsigned idx;
    259 
    260   clear_hwcdefs ();
    261   if (!defstring || !strlen (defstring))
    262     {
    263       err = HWCFUNCS_ERROR_HWCARGS;
    264       goto ext_hw_install_end;
    265     }
    266   ds = strdup (defstring);
    267   if (!ds)
    268     {
    269       err = HWCFUNCS_ERROR_HWCINIT;
    270       goto ext_hw_install_end;
    271     }
    272   dsp = ds;
    273 
    274   for (idx = 0; idx < MAX_PICS && *dsp; idx++)
    275     {
    276       char *name = NULL;
    277       char *int_name = NULL;
    278       regno_t reg = REGNO_ANY;
    279       ABST_type memop = ABST_NONE;
    280       int interval = 0;
    281       int timecvt = 0;
    282       unsigned sort_order = (unsigned) - 1;
    283 
    284       /* name */
    285       name = dsp;
    286       dsp = strchr (dsp, ':');
    287       if (dsp == NULL)
    288 	{
    289 	  err = HWCFUNCS_ERROR_HWCARGS;
    290 	  goto ext_hw_install_end;
    291 	}
    292       *dsp++ = (char) 0;
    293 
    294       /* int_name */
    295       int_name = dsp;
    296       dsp = strchr (dsp, ':');
    297       if (dsp == NULL)
    298 	{
    299 	  err = HWCFUNCS_ERROR_HWCARGS;
    300 	  goto ext_hw_install_end;
    301 	}
    302       *dsp++ = (char) 0;
    303 
    304       /* reg_num */
    305       reg = (int) strtol (dsp, &dsp, 0);
    306       if (*dsp++ != ':')
    307 	{
    308 	  err = HWCFUNCS_ERROR_HWCARGS;
    309 	  goto ext_hw_install_end;
    310 	}
    311       if (reg < 0 && reg != -1)
    312 	{
    313 	  err = HWCFUNCS_ERROR_HWCARGS;
    314 	  goto ext_hw_install_end;
    315 	}
    316       if (reg >= 0)
    317 	hwcdef[idx].reg_num = reg;
    318 
    319       /* val */
    320       interval = (int) strtol (dsp, &dsp, 0);
    321       if (*dsp++ != ':')
    322 	{
    323 	  err = HWCFUNCS_ERROR_HWCARGS;
    324 	  goto ext_hw_install_end;
    325 	}
    326       if (interval < 0)
    327 	{
    328 	  err = HWCFUNCS_ERROR_HWCARGS;
    329 	  goto ext_hw_install_end;
    330 	}
    331       hwcdef[idx].val = interval;
    332 
    333       /* min_time */
    334       /*
    335        * This is a new field.
    336        * An old launcher (dbx, etc.) would not include it.
    337        * Detect the presence of the field by the char 'm'.
    338        */
    339       if (*dsp == 'm')
    340 	{
    341 	  long long tmp_ll = 0;
    342 	  dsp++;
    343 	  tmp_ll = strtoll (dsp, &dsp, 0);
    344 	  if (*dsp++ != ':')
    345 	    {
    346 	      err = HWCFUNCS_ERROR_HWCARGS;
    347 	      goto ext_hw_install_end;
    348 	    }
    349 	  if (tmp_ll < 0)
    350 	    {
    351 	      err = HWCFUNCS_ERROR_HWCARGS;
    352 	      goto ext_hw_install_end;
    353 	    }
    354 	  hwcdef[idx].min_time = tmp_ll;
    355 	}
    356       else
    357 	hwcdef[idx].min_time = 0;
    358 
    359       /* sort_order */
    360       sort_order = (int) strtoul (dsp, &dsp, 0);
    361       if (*dsp++ != ':')
    362 	{
    363 	  err = HWCFUNCS_ERROR_HWCARGS;
    364 	  goto ext_hw_install_end;
    365 	}
    366       hwcdef[idx].sort_order = sort_order;
    367 
    368       /* timecvt */
    369       timecvt = (int) strtol (dsp, &dsp, 0);
    370       if (*dsp++ != ':')
    371 	{
    372 	  err = HWCFUNCS_ERROR_HWCARGS;
    373 	  goto ext_hw_install_end;
    374 	}
    375       hwcdef[idx].timecvt = timecvt;
    376 
    377       /* memop */
    378       memop = (ABST_type) strtol (dsp, &dsp, 0);
    379       if (*dsp != 0 && *dsp++ != ',')
    380 	{
    381 	  err = HWCFUNCS_ERROR_HWCARGS;
    382 	  goto ext_hw_install_end;
    383 	}
    384       hwcdef[idx].memop = memop;
    385       if (*name)
    386 	hwcdef[idx].name = strdup (name);
    387       else
    388 	hwcdef[idx].name = strdup (int_name);
    389       if (*int_name)
    390 	hwcdef[idx].int_name = strdup (int_name);
    391       else
    392 	hwcdef[idx].int_name = strdup (name);
    393       ctrdefprint (DBG_LT1, "hwcfuncs: process_data_descriptor", &hwcdef[idx]);
    394     }
    395 
    396   if (*dsp)
    397     {
    398       TprintfT (DBG_LT0, "hwcfuncs: ERROR: process_data_descriptor(): "
    399 		"ctr string had some trailing garbage:"
    400 		" '%s'\n", dsp);
    401       err = HWCFUNCS_ERROR_HWCARGS;
    402       goto ext_hw_install_end;
    403     }
    404   free (ds);
    405   hwcdef_cnt = idx;
    406   return 0;
    407 
    408 ext_hw_install_end:
    409   if (dsp && *dsp)
    410     {
    411       TprintfT (DBG_LT0, "hwcfuncs: ERROR: process_data_descriptor(): "
    412 		" syntax error just before:"
    413 		" '%s;\n", dsp);
    414       logerr (GTXT ("Data descriptor syntax error near `%s'\n"), dsp);
    415     }
    416   else
    417     logerr (GTXT ("Data descriptor syntax error\n"));
    418   free (ds);
    419   return err;
    420 }
    421 
    422 /* initialize hwcdef[] based on user's counter definitions */
    423 static int
    424 process_hwcentrylist (const Hwcentry* entries[], unsigned numctrs)
    425 {
    426   int err = 0;
    427   clear_hwcdefs ();
    428   if (numctrs > cpcN_npics)
    429     {
    430       logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
    431       return HWCFUNCS_ERROR_HWCARGS;
    432     }
    433   for (unsigned idx = 0; idx < numctrs; idx++)
    434     {
    435       Hwcentry *phwcdef = &hwcdef[idx];
    436       *phwcdef = *entries[idx];
    437       if (phwcdef->name)
    438 	phwcdef->name = strdup (phwcdef->name);
    439       else
    440 	phwcdef->name = "NULL";
    441       if (phwcdef->int_name)
    442 	phwcdef->int_name = strdup (phwcdef->int_name);
    443       else
    444 	phwcdef->int_name = "NULL";
    445       if (phwcdef->val < 0)
    446 	{
    447 	  logerr (GTXT ("Negative interval specified for HW counter `%s'\n"), /*!*/
    448 		  phwcdef->name);
    449 	  err = HWCFUNCS_ERROR_HWCARGS;
    450 	  break;
    451 	}
    452       ctrdefprint (DBG_LT1, "hwcfuncs: process_hwcentrylist", phwcdef);
    453     }
    454   if (!err)
    455     hwcdef_cnt = numctrs;
    456   return err;
    457 }
    458 
    459 /* see hwcfuncs.h */
    460 IS_GLOBAL void *
    461 hwcfuncs_parse_attrs (const char *countername, hwcfuncs_attr_t attrs[],
    462 		      unsigned max_attrs, uint_t *pnum_attrs, char**errstring)
    463 {
    464   char *head = NULL;
    465   char *tail = NULL;
    466   uint_t nattrs = 0;
    467   char *counter_copy;
    468   int success = 0;
    469   char errbuf[512];
    470   errbuf[0] = 0;
    471   counter_copy = strdup (countername);
    472 
    473   /* advance pointer to first attribute */
    474   tail = strchr (counter_copy, HWCFUNCS_PARSE_ATTR);
    475   if (tail)
    476     *tail = 0;
    477 
    478   /* remove regno and value, if supplied */
    479   {
    480     char *tmp = strchr (counter_copy, HWCFUNCS_PARSE_REGNUM);
    481     if (tmp)
    482       *tmp = 0;
    483     tmp = strchr (counter_copy, HWCFUNCS_PARSE_VALUE);
    484     if (tmp)
    485       *tmp = 0;
    486   }
    487 
    488   while (tail)
    489     {
    490       char *pch;
    491       if (nattrs >= max_attrs)
    492 	{
    493 	  snprintf (errbuf, sizeof (errbuf),
    494 		    GTXT ("Too many attributes defined in `%s'"),
    495 		    countername);
    496 	  goto mycpc2_parse_attrs_end;
    497 	}
    498       /* get attribute name */
    499       head = tail + 1;
    500       tail = strchr (head, HWCFUNCS_PARSE_EQUAL);
    501       if (!tail)
    502 	{
    503 	  snprintf (errbuf, sizeof (errbuf),
    504 		    GTXT ("Missing value for attribute `%s' in `%s'"),
    505 		    head, countername);
    506 	  goto mycpc2_parse_attrs_end;
    507 	}
    508       *tail = 0; /* null terminate current component */
    509       attrs[nattrs].ca_name = head;
    510 
    511       /* get attribute value */
    512       head = tail + 1;
    513       tail = strchr (head, HWCFUNCS_PARSE_ATTR);
    514       if (tail)
    515 	*tail = 0; /* null terminate current component */
    516       attrs[nattrs].ca_val = strtoull (head, &pch, 0);
    517       if (pch == head)
    518 	{
    519 	  snprintf (errbuf, sizeof (errbuf),
    520 		    GTXT ("Illegal value for attribute `%s' in `%s'"),
    521 		    attrs[nattrs].ca_name, countername);
    522 	  goto mycpc2_parse_attrs_end;
    523 	}
    524       TprintfT (DBG_LT0, "hwcfuncs: pic_: '%s', attribute[%u]"
    525 		" '%s' = 0x%llx\n",
    526 		counter_copy, nattrs, attrs[nattrs].ca_name,
    527 		(long long unsigned int) attrs[nattrs].ca_val);
    528 
    529       nattrs++;
    530     }
    531   success = 1;
    532 
    533 mycpc2_parse_attrs_end:
    534   *pnum_attrs = nattrs;
    535   if (success)
    536     {
    537       if (errstring)
    538 	*errstring = NULL;
    539     }
    540   else
    541     {
    542       if (errstring)
    543 	*errstring = strdup (errbuf);
    544       free (counter_copy);
    545       counter_copy = NULL;
    546     }
    547   return counter_copy;
    548 }
    549 
    550 IS_GLOBAL void
    551 hwcfuncs_parse_ctr (const char *counter_def, int *pplus, char **pnameOnly,
    552 		    char **pattrs, char **pregstr, regno_t *pregno)
    553 {
    554   char *nameptr, *copy, *slash, *attr_delim;
    555   int plus;
    556   regno_t regno;
    557   nameptr = copy = strdup (counter_def);
    558 
    559   /* plus */
    560   plus = 0;
    561   if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK)
    562     {
    563       plus = 1;
    564       nameptr++;
    565     }
    566   else if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK_OFF)
    567     {
    568       plus = -1;
    569       nameptr++;
    570     }
    571   if (pplus)
    572     *pplus = plus;
    573 
    574   /* regno */
    575   regno = REGNO_ANY;
    576   if (pregstr)
    577     *pregstr = NULL;
    578   slash = strchr (nameptr, HWCFUNCS_PARSE_REGNUM);
    579   if (slash != NULL)
    580     {
    581       /* the remaining string should be a number > 0 */
    582       if (pregstr)
    583 	*pregstr = strdup (slash);
    584       char *endchar = NULL;
    585       regno = (regno_t) strtol (slash + 1, &endchar, 0);
    586       if (*endchar != 0)
    587 	regno = -2;
    588       if (*(slash + 1) == '-')
    589 	regno = -2;
    590       /* terminate previous element up to slash */
    591       *slash = 0;
    592     }
    593   if (pregno)
    594     *pregno = regno;
    595 
    596   /* attrs */
    597   if (pattrs)
    598     *pattrs = NULL;
    599   attr_delim = strchr (nameptr, HWCFUNCS_PARSE_ATTR);
    600   if (attr_delim != NULL)
    601     {
    602       if (pattrs)
    603 	*pattrs = strdup (attr_delim);
    604       /* terminate previous element up to attr_delim */
    605       *attr_delim++ = 0;
    606     }
    607   if (pnameOnly)
    608     *pnameOnly = strdup (nameptr);
    609   free (copy);
    610 }
    611 
    612 /* create counters */
    613 IS_GLOBAL int
    614 hwcfuncs_bind_descriptor (const char *defstring)
    615 {
    616   int err = process_data_descriptor (defstring);
    617   if (err)
    618     {
    619       TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_descriptor failed\n");
    620       return err;
    621     }
    622   err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
    623   return err;
    624 }
    625 
    626 /* see hwcfuncs.h */
    627 IS_GLOBAL int
    628 hwcfuncs_bind_hwcentry (const Hwcentry* entries[], unsigned numctrs)
    629 {
    630   int err = -1;
    631   err = process_hwcentrylist (entries, numctrs);
    632   if (err)
    633     {
    634       TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_hwcentry\n");
    635       return err;
    636     }
    637   err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
    638   return err;
    639 }
    640 
    641 /* see hwcfuncs.h */
    642 IS_GLOBAL Hwcentry **
    643 hwcfuncs_get_ctrs (unsigned *defcnt)
    644 {
    645   if (defcnt)
    646     *defcnt = hwcdef_cnt;
    647   return hwctable;
    648 }
    649 
    650 /* return 1 if <regno> is in Hwcentry's list */
    651 IS_GLOBAL int
    652 regno_is_valid (const Hwcentry * pctr, regno_t regno)
    653 {
    654   regno_t *reg_list = pctr->reg_list;
    655   if (REG_LIST_IS_EMPTY (reg_list))
    656     return 0;
    657   if (regno == REGNO_ANY)   /* wildcard */
    658     return 1;
    659   for (int ii = 0; ii < MAX_PICS; ii++)
    660     {
    661       regno_t tmp = reg_list[ii];
    662       if (REG_LIST_EOL (tmp))   /* end of list */
    663 	break;
    664       if (tmp == regno)     /* is in list */
    665 	return 1;
    666     }
    667   return 0;
    668 }
    669 
    670 /* supplied by hwcdrv_api drivers */
    671 IS_GLOBAL int
    672 hwcfuncs_assign_regnos (Hwcentry* entries[],
    673 			unsigned numctrs)
    674 {
    675   if (numctrs > cpcN_npics)
    676     {
    677       logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
    678       return HWCFUNCS_ERROR_HWCARGS;
    679     }
    680   return hwcdrv_driver->hwcdrv_assign_regnos (entries, numctrs);
    681 }
    682 
    683 extern hwcdrv_api_t hwcdrv_pcl_api;
    684 static int hwcdrv_driver_inited = 0;
    685 
    686 hwcdrv_api_t *
    687 get_hwcdrv ()
    688 {
    689   if (hwcdrv_driver_inited)
    690     return hwcdrv_driver;
    691   hwcdrv_driver_inited = 1;
    692   cpcN_npics = 0;
    693   for (int i = 0; i < MAX_PICS; i++)
    694     hwctable[i] = &hwcdef[i];
    695   hwcdrv_driver = &hwcdrv_pcl_api;
    696   hwcdrv_driver->hwcdrv_init_status = hwcdrv_driver->hwcdrv_init (NULL, NULL);
    697   if (hwcdrv_driver->hwcdrv_init_status == 0)
    698     {
    699       hwcdrv_driver->hwcdrv_get_info (NULL, NULL, &cpcN_npics, NULL, NULL);
    700       return hwcdrv_driver;
    701     }
    702   hwcdrv_driver = &hwcdrv_default;
    703   return hwcdrv_driver;
    704 }
    705