1  /* Copyright (C) 2021-2023 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  }