Home | History | Annotate | Line # | Download | only in common
hwcfuncs.c revision 1.1.1.2
      1 /* Copyright (C) 2021-2024 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     return HWCFUNCS_ERROR_HWCARGS;
    263   ds = strdup (defstring);
    264   if (!ds)
    265     return HWCFUNCS_ERROR_HWCINIT;
    266   dsp = ds;
    267   for (idx = 0; idx < MAX_PICS && *dsp; idx++)
    268     {
    269       char *name = NULL;
    270       char *int_name = NULL;
    271       regno_t reg = REGNO_ANY;
    272       ABST_type memop = ABST_NONE;
    273       int interval = 0;
    274       int timecvt = 0;
    275       unsigned sort_order = (unsigned) - 1;
    276 
    277       // Read use_perf_event_type, type, config
    278       hwcdef[idx].use_perf_event_type = (int) strtol (dsp, &dsp, 0);
    279       if (*dsp++ != ':')
    280 	{
    281 	  err = HWCFUNCS_ERROR_HWCARGS;
    282 	  break;
    283 	}
    284       hwcdef[idx].type = (int) strtol (dsp, &dsp, 0);
    285       if (*dsp++ != ':')
    286 	{
    287 	  err = HWCFUNCS_ERROR_HWCARGS;
    288 	  break;
    289 	}
    290       hwcdef[idx].config = strtol (dsp, &dsp, 0);
    291       if (*dsp++ != ':')
    292 	{
    293 	  err = HWCFUNCS_ERROR_HWCARGS;
    294 	  break;
    295 	}
    296 
    297       /* name */
    298       name = dsp;
    299       dsp = strchr (dsp, ':');
    300       if (dsp == NULL)
    301 	{
    302 	  err = HWCFUNCS_ERROR_HWCARGS;
    303 	  break;
    304 	}
    305       *dsp++ = (char) 0;
    306 
    307       /* int_name */
    308       int_name = dsp;
    309       dsp = strchr (dsp, ':');
    310       if (dsp == NULL)
    311 	{
    312 	  err = HWCFUNCS_ERROR_HWCARGS;
    313 	  break;
    314 	}
    315       *dsp++ = (char) 0;
    316 
    317       /* reg_num */
    318       reg = (int) strtol (dsp, &dsp, 0);
    319       if (*dsp++ != ':')
    320 	{
    321 	  err = HWCFUNCS_ERROR_HWCARGS;
    322 	  break;
    323 	}
    324       if (reg < 0 && reg != -1)
    325 	{
    326 	  err = HWCFUNCS_ERROR_HWCARGS;
    327 	  break;
    328 	}
    329       if (reg >= 0)
    330 	hwcdef[idx].reg_num = reg;
    331 
    332       /* val */
    333       interval = (int) strtol (dsp, &dsp, 0);
    334       if (*dsp++ != ':')
    335 	{
    336 	  err = HWCFUNCS_ERROR_HWCARGS;
    337 	  break;
    338 	}
    339       if (interval < 0)
    340 	{
    341 	  err = HWCFUNCS_ERROR_HWCARGS;
    342 	  break;
    343 	}
    344       hwcdef[idx].val = interval;
    345 
    346       /* min_time */
    347       if (*dsp == 'm')
    348 	{
    349 	  long long tmp_ll = 0;
    350 	  dsp++;
    351 	  tmp_ll = strtoll (dsp, &dsp, 0);
    352 	  if (*dsp++ != ':')
    353 	    {
    354 	      err = HWCFUNCS_ERROR_HWCARGS;
    355 	      break;
    356 	    }
    357 	  if (tmp_ll < 0)
    358 	    {
    359 	      err = HWCFUNCS_ERROR_HWCARGS;
    360 	      break;
    361 	    }
    362 	  hwcdef[idx].min_time = tmp_ll;
    363 	}
    364       else
    365 	hwcdef[idx].min_time = 0;
    366 
    367       /* sort_order */
    368       sort_order = (int) strtoul (dsp, &dsp, 0);
    369       if (*dsp++ != ':')
    370 	{
    371 	  err = HWCFUNCS_ERROR_HWCARGS;
    372 	  break;
    373 	}
    374       hwcdef[idx].sort_order = sort_order;
    375 
    376       /* timecvt */
    377       timecvt = (int) strtol (dsp, &dsp, 0);
    378       if (*dsp++ != ':')
    379 	{
    380 	  err = HWCFUNCS_ERROR_HWCARGS;
    381 	  break;
    382 	}
    383       hwcdef[idx].timecvt = timecvt;
    384 
    385       /* memop */
    386       memop = (ABST_type) strtol (dsp, &dsp, 0);
    387       if (*dsp != 0 && *dsp++ != ',')
    388 	{
    389 	  err = HWCFUNCS_ERROR_HWCARGS;
    390 	  break;
    391 	}
    392       hwcdef[idx].memop = memop;
    393       if (*name)
    394 	hwcdef[idx].name = strdup (name);
    395       else
    396 	hwcdef[idx].name = strdup (int_name);
    397       if (*int_name)
    398 	hwcdef[idx].int_name = strdup (int_name);
    399       else
    400 	hwcdef[idx].int_name = strdup (name);
    401       ctrdefprint (DBG_LT1, "hwcfuncs: process_data_descriptor", &hwcdef[idx]);
    402     }
    403 
    404   if (*dsp)
    405     err = HWCFUNCS_ERROR_HWCARGS;
    406   if (err != 0)
    407     logerr (GTXT ("Data descriptor syntax error near `%s'\n"), dsp);
    408   else
    409     hwcdef_cnt = idx;
    410   free (ds);
    411   return err;
    412 }
    413 
    414 /* initialize hwcdef[] based on user's counter definitions */
    415 static int
    416 process_hwcentrylist (const Hwcentry* entries[], unsigned numctrs)
    417 {
    418   int err = 0;
    419   clear_hwcdefs ();
    420   if (numctrs > cpcN_npics)
    421     {
    422       logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
    423       return HWCFUNCS_ERROR_HWCARGS;
    424     }
    425   for (unsigned idx = 0; idx < numctrs; idx++)
    426     {
    427       Hwcentry *phwcdef = &hwcdef[idx];
    428       *phwcdef = *entries[idx];
    429       if (phwcdef->name)
    430 	phwcdef->name = strdup (phwcdef->name);
    431       else
    432 	phwcdef->name = "NULL";
    433       if (phwcdef->int_name)
    434 	phwcdef->int_name = strdup (phwcdef->int_name);
    435       else
    436 	phwcdef->int_name = "NULL";
    437       if (phwcdef->val < 0)
    438 	{
    439 	  logerr (GTXT ("Negative interval specified for HW counter `%s'\n"), /*!*/
    440 		  phwcdef->name);
    441 	  err = HWCFUNCS_ERROR_HWCARGS;
    442 	  break;
    443 	}
    444       ctrdefprint (DBG_LT1, "hwcfuncs: process_hwcentrylist", phwcdef);
    445     }
    446   if (!err)
    447     hwcdef_cnt = numctrs;
    448   return err;
    449 }
    450 
    451 /* see hwcfuncs.h */
    452 IS_GLOBAL void *
    453 hwcfuncs_parse_attrs (const char *countername, hwcfuncs_attr_t attrs[],
    454 		      unsigned max_attrs, uint_t *pnum_attrs, char**errstring)
    455 {
    456   char *head = NULL;
    457   char *tail = NULL;
    458   uint_t nattrs = 0;
    459   char *counter_copy;
    460   int success = 0;
    461   char errbuf[512];
    462   errbuf[0] = 0;
    463   counter_copy = strdup (countername);
    464 
    465   /* advance pointer to first attribute */
    466   tail = strchr (counter_copy, HWCFUNCS_PARSE_ATTR);
    467   if (tail)
    468     *tail = 0;
    469 
    470   /* remove regno and value, if supplied */
    471   {
    472     char *tmp = strchr (counter_copy, HWCFUNCS_PARSE_REGNUM);
    473     if (tmp)
    474       *tmp = 0;
    475     tmp = strchr (counter_copy, HWCFUNCS_PARSE_VALUE);
    476     if (tmp)
    477       *tmp = 0;
    478   }
    479 
    480   while (tail)
    481     {
    482       char *pch;
    483       if (nattrs >= max_attrs)
    484 	{
    485 	  snprintf (errbuf, sizeof (errbuf),
    486 		    GTXT ("Too many attributes defined in `%s'"),
    487 		    countername);
    488 	  goto mycpc2_parse_attrs_end;
    489 	}
    490       /* get attribute name */
    491       head = tail + 1;
    492       tail = strchr (head, HWCFUNCS_PARSE_EQUAL);
    493       if (!tail)
    494 	{
    495 	  snprintf (errbuf, sizeof (errbuf),
    496 		    GTXT ("Missing value for attribute `%s' in `%s'"),
    497 		    head, countername);
    498 	  goto mycpc2_parse_attrs_end;
    499 	}
    500       *tail = 0; /* null terminate current component */
    501       attrs[nattrs].ca_name = head;
    502 
    503       /* get attribute value */
    504       head = tail + 1;
    505       tail = strchr (head, HWCFUNCS_PARSE_ATTR);
    506       if (tail)
    507 	*tail = 0; /* null terminate current component */
    508       attrs[nattrs].ca_val = strtoull (head, &pch, 0);
    509       if (pch == head)
    510 	{
    511 	  snprintf (errbuf, sizeof (errbuf),
    512 		    GTXT ("Illegal value for attribute `%s' in `%s'"),
    513 		    attrs[nattrs].ca_name, countername);
    514 	  goto mycpc2_parse_attrs_end;
    515 	}
    516       TprintfT (DBG_LT0, "hwcfuncs: pic_: '%s', attribute[%u]"
    517 		" '%s' = 0x%llx\n",
    518 		counter_copy, nattrs, attrs[nattrs].ca_name,
    519 		(long long unsigned int) attrs[nattrs].ca_val);
    520 
    521       nattrs++;
    522     }
    523   success = 1;
    524 
    525 mycpc2_parse_attrs_end:
    526   *pnum_attrs = nattrs;
    527   if (success)
    528     {
    529       if (errstring)
    530 	*errstring = NULL;
    531     }
    532   else
    533     {
    534       if (errstring)
    535 	*errstring = strdup (errbuf);
    536       free (counter_copy);
    537       counter_copy = NULL;
    538     }
    539   return counter_copy;
    540 }
    541 
    542 IS_GLOBAL void
    543 hwcfuncs_parse_ctr (const char *counter_def, int *pplus, char **pnameOnly,
    544 		    char **pattrs, char **pregstr, regno_t *pregno)
    545 {
    546   char *nameptr, *copy, *slash, *attr_delim;
    547   int plus;
    548   regno_t regno;
    549   nameptr = copy = strdup (counter_def);
    550 
    551   /* plus */
    552   plus = 0;
    553   if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK)
    554     {
    555       plus = 1;
    556       nameptr++;
    557     }
    558   else if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK_OFF)
    559     {
    560       plus = -1;
    561       nameptr++;
    562     }
    563   if (pplus)
    564     *pplus = plus;
    565 
    566   /* regno */
    567   regno = REGNO_ANY;
    568   if (pregstr)
    569     *pregstr = NULL;
    570   slash = strchr (nameptr, HWCFUNCS_PARSE_REGNUM);
    571   if (slash != NULL)
    572     {
    573       /* the remaining string should be a number > 0 */
    574       if (pregstr)
    575 	*pregstr = strdup (slash);
    576       char *endchar = NULL;
    577       regno = (regno_t) strtol (slash + 1, &endchar, 0);
    578       if (*endchar != 0)
    579 	regno = -2;
    580       if (*(slash + 1) == '-')
    581 	regno = -2;
    582       /* terminate previous element up to slash */
    583       *slash = 0;
    584     }
    585   if (pregno)
    586     *pregno = regno;
    587 
    588   /* attrs */
    589   if (pattrs)
    590     *pattrs = NULL;
    591   attr_delim = strchr (nameptr, HWCFUNCS_PARSE_ATTR);
    592   if (attr_delim != NULL)
    593     {
    594       if (pattrs)
    595 	*pattrs = strdup (attr_delim);
    596       /* terminate previous element up to attr_delim */
    597       *attr_delim++ = 0;
    598     }
    599   if (pnameOnly)
    600     *pnameOnly = strdup (nameptr);
    601   free (copy);
    602 }
    603 
    604 /* create counters */
    605 IS_GLOBAL int
    606 hwcfuncs_bind_descriptor (const char *defstring)
    607 {
    608   int err = process_data_descriptor (defstring);
    609   if (err)
    610     {
    611       TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_descriptor failed\n");
    612       return err;
    613     }
    614   err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
    615   return err;
    616 }
    617 
    618 /* see hwcfuncs.h */
    619 IS_GLOBAL int
    620 hwcfuncs_bind_hwcentry (const Hwcentry* entries[], unsigned numctrs)
    621 {
    622   int err = -1;
    623   err = process_hwcentrylist (entries, numctrs);
    624   if (err)
    625     {
    626       TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_hwcentry\n");
    627       return err;
    628     }
    629   err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
    630   return err;
    631 }
    632 
    633 /* see hwcfuncs.h */
    634 IS_GLOBAL Hwcentry **
    635 hwcfuncs_get_ctrs (unsigned *defcnt)
    636 {
    637   if (defcnt)
    638     *defcnt = hwcdef_cnt;
    639   return hwctable;
    640 }
    641 
    642 /* return 1 if <regno> is in Hwcentry's list */
    643 IS_GLOBAL int
    644 regno_is_valid (const Hwcentry * pctr, regno_t regno)
    645 {
    646   regno_t *reg_list = pctr->reg_list;
    647   if (REG_LIST_IS_EMPTY (reg_list))
    648     return 0;
    649   if (regno == REGNO_ANY)   /* wildcard */
    650     return 1;
    651   for (int ii = 0; ii < MAX_PICS; ii++)
    652     {
    653       regno_t tmp = reg_list[ii];
    654       if (REG_LIST_EOL (tmp))   /* end of list */
    655 	break;
    656       if (tmp == regno)     /* is in list */
    657 	return 1;
    658     }
    659   return 0;
    660 }
    661 
    662 /* supplied by hwcdrv_api drivers */
    663 IS_GLOBAL int
    664 hwcfuncs_assign_regnos (Hwcentry* entries[],
    665 			unsigned numctrs)
    666 {
    667   if (numctrs > cpcN_npics)
    668     {
    669       logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
    670       return HWCFUNCS_ERROR_HWCARGS;
    671     }
    672   return hwcdrv_driver->hwcdrv_assign_regnos (entries, numctrs);
    673 }
    674 
    675 extern hwcdrv_api_t hwcdrv_pcl_api;
    676 static int hwcdrv_driver_inited = 0;
    677 
    678 hwcdrv_api_t *
    679 get_hwcdrv ()
    680 {
    681   if (hwcdrv_driver_inited)
    682     return hwcdrv_driver;
    683   hwcdrv_driver_inited = 1;
    684   cpcN_npics = 0;
    685   for (int i = 0; i < MAX_PICS; i++)
    686     hwctable[i] = &hwcdef[i];
    687   hwcdrv_driver = &hwcdrv_pcl_api;
    688   hwcdrv_driver->hwcdrv_init_status = hwcdrv_driver->hwcdrv_init (NULL, NULL);
    689   if (hwcdrv_driver->hwcdrv_init_status == 0)
    690     {
    691       hwcdrv_driver->hwcdrv_get_info (NULL, NULL, &cpcN_npics, NULL, NULL);
    692       return hwcdrv_driver;
    693     }
    694   hwcdrv_driver = &hwcdrv_default;
    695   return hwcdrv_driver;
    696 }
    697