/* Copyright (C) 2021-2024 Free Software Foundation, Inc.
  Contributed by Oracle.

  This file is part of GNU Binutils.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 3, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, 51 Franklin Street - Fifth Floor, Boston,
  MA 02110-1301, USA.  */

/* Hardware counter profiling */
#include "hwcdrv.h"
#include "hwcfuncs.h"

/*---------------------------------------------------------------------------*/
/* macros */

#define IS_GLOBAL           /* Mark global symbols */
#define HWCDRV_API static   /* Mark functions used by hwcdrv API */

/*---------------------------------------------------------------------------*/
/* static variables */
static uint_t cpcN_npics;
static char hwcfuncs_errmsg_buf[1024];
static int hwcfuncs_errmsg_enabled = 1;
static int hwcfuncs_errmsg_valid;

/* --- user counter selections and options */
static unsigned hwcdef_cnt; /* number of *active* hardware counters */
static Hwcentry hwcdef[MAX_PICS]; /* HWC definitions */
static Hwcentry *hwctable[MAX_PICS]; /* HWC definitions */

/* --- drivers --- */

// default driver

HWCDRV_API int
hwcdrv_init (hwcfuncs_abort_fn_t abort_ftn, int* tsd_sz)
{
 return -1;
}

HWCDRV_API void
hwcdrv_get_info (
                int * cpuver, const char ** cciname,
                uint_t * npics, const char ** docref, uint64_t* support) { }

HWCDRV_API int
hwcdrv_enable_mt (hwcfuncs_tsd_get_fn_t tsd_ftn)
{
 return -1;
}

HWCDRV_API int
hwcdrv_get_descriptions (hwcf_hwc_cb_t *hwc_find_action,
                        hwcf_attr_cb_t *attr_find_action)
{
 return 0;
}

HWCDRV_API int
hwcdrv_assign_regnos (Hwcentry *entries[], unsigned numctrs)
{
 return -1;
}

HWCDRV_API int
hwcdrv_create_counters (unsigned hwcdef_cnt, Hwcentry *hwcdef)
{
 return -1;
}

HWCDRV_API int
hwcdrv_read_events (hwc_event_t *events, hwc_event_samples_t*samples)
{
 return -1;
}

HWCDRV_API int
hwcdrv_start (void)
{
 return -1;
}

HWCDRV_API int
hwcdrv_overflow (siginfo_t *si, hwc_event_t *s, hwc_event_t *t)
{
 return 0;
}

HWCDRV_API int
hwcdrv_sighlr_restart (const hwc_event_t *sample)
{
 return -1;
}

HWCDRV_API int
hwcdrv_lwp_suspend (void)
{
 return -1;
}

HWCDRV_API int
hwcdrv_lwp_resume (void)
{
 return -1;
}

HWCDRV_API int
hwcdrv_free_counters (void)
{
 return 0;
}

HWCDRV_API int
hwcdrv_lwp_init (void)
{
 return 0;
}

HWCDRV_API void
hwcdrv_lwp_fini (void) { }

static hwcdrv_api_t hwcdrv_default = {
 hwcdrv_init,
 hwcdrv_get_info,
 hwcdrv_enable_mt,
 hwcdrv_get_descriptions,
 hwcdrv_assign_regnos,
 hwcdrv_create_counters,
 hwcdrv_start,
 hwcdrv_overflow,
 hwcdrv_read_events,
 hwcdrv_sighlr_restart,
 hwcdrv_lwp_suspend,
 hwcdrv_lwp_resume,
 hwcdrv_free_counters,
 hwcdrv_lwp_init,
 hwcdrv_lwp_fini,
 -1                        // hwcdrv_init_status
};

static hwcdrv_api_t *hwcdrv_driver = &hwcdrv_default;


/*---------------------------------------------------------------------------*/
/* misc */

/* print a counter definition (for debugging) */
static void
ctrdefprint (int dbg_lvl, const char * hdr, Hwcentry*phwcdef)
{
 TprintfT (dbg_lvl, "%s: name='%s', int_name='%s',"
           " reg_num=%d, timecvt=%d, memop=%d, "
           "interval=%d, tag=%u, reg_list=%p\n",
           hdr, phwcdef->name, phwcdef->int_name, phwcdef->reg_num,
           phwcdef->timecvt, phwcdef->memop, phwcdef->val,
           phwcdef->sort_order, phwcdef->reg_list);
}

/*---------------------------------------------------------------------------*/
/* errmsg buffering */

/* errmsg buffering is needed only because the most descriptive error
  messages from CPC are delivered using a callback mechanism.
  hwcfuncs_errmsg_get() should only be used during initialization, and
  ideally,  only to provide feedback to an end user when his counters can't
  be bound to HW.
*/
IS_GLOBAL char *
hwcfuncs_errmsg_get (char *buf, size_t bufsize, int enable)
{
 hwcfuncs_errmsg_enabled = 0;
 if (buf && bufsize)
   {
     if (hwcfuncs_errmsg_valid)
       {
         strncpy (buf, hwcfuncs_errmsg_buf, bufsize);
         buf[bufsize - 1] = 0;
       }
     else
       *buf = 0;
   }
 hwcfuncs_errmsg_buf[0] = 0;
 hwcfuncs_errmsg_valid = 0;
 hwcfuncs_errmsg_enabled = enable;
 return buf;
}

/* used by cpc to log an error */
IS_GLOBAL void
hwcfuncs_int_capture_errmsg (const char *fn, int subcode,
                            const char *fmt, va_list ap)
{
 if (hwcfuncs_errmsg_enabled &&
     !hwcfuncs_errmsg_valid)
   {
     vsnprintf (hwcfuncs_errmsg_buf, sizeof (hwcfuncs_errmsg_buf), fmt, ap);
     TprintfT (DBG_LT0, "hwcfuncs: cpcN_capture_errmsg(): %s\n",
               hwcfuncs_errmsg_buf);
     hwcfuncs_errmsg_valid = 1;
   }
 return;
}

/* Log an internal error to the CPC error buffer.
* Note: only call this during init functions.
* Note: when most cpc calls fail, they will call cpcN_capture_errmsg()
*   directly, so only call logerr() when a non-cpc function fails.
*/
IS_GLOBAL void
hwcfuncs_int_logerr (const char *format, ...)
{
 va_list va;
 va_start (va, format);
 hwcfuncs_int_capture_errmsg ("logerr", 0, format, va);
 va_end (va);
}

/* utils to parse counter strings */
static void
clear_hwcdefs ()
{
 for (unsigned idx = 0; idx < MAX_PICS; idx++)
   {
     static Hwcentry empty;
     hwcdef[idx] = empty; // leaks strings and reg_list array
     hwcdef[idx].reg_num = REGNO_ANY;
     hwcdef[idx].val = -1;
     hwcdef[idx].sort_order = -1;
   }
}

/* initialize hwcdef[] based on user's counter definitions */
static int
process_data_descriptor (const char *defstring)
{
 /*
  * <defstring> format should be of format
  *  :%s:%s:0x%x:%d:%lld:%d:%d:0x%x[,%s...repeat for each ctr]
  * where the counter fields are:
  *  :<userName>:<internalCtr>:<register>:<timeoutVal>[:m<min_time>]:<tag>:<timecvt>:<memop>
  * See Coll_Ctrl::build_data_desc().
  */
 int err = 0;
 char *ds = NULL;
 char *dsp = NULL;
 unsigned idx;

 clear_hwcdefs ();
 if (!defstring || !strlen (defstring))
   return HWCFUNCS_ERROR_HWCARGS;
 ds = strdup (defstring);
 if (!ds)
   return HWCFUNCS_ERROR_HWCINIT;
 dsp = ds;
 for (idx = 0; idx < MAX_PICS && *dsp; idx++)
   {
     char *name = NULL;
     char *int_name = NULL;
     regno_t reg = REGNO_ANY;
     ABST_type memop = ABST_NONE;
     int interval = 0;
     int timecvt = 0;
     unsigned sort_order = (unsigned) - 1;

     // Read use_perf_event_type, type, config
     hwcdef[idx].use_perf_event_type = (int) strtol (dsp, &dsp, 0);
     if (*dsp++ != ':')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     hwcdef[idx].type = (int) strtol (dsp, &dsp, 0);
     if (*dsp++ != ':')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     hwcdef[idx].config = strtol (dsp, &dsp, 0);
     if (*dsp++ != ':')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }

     /* name */
     name = dsp;
     dsp = strchr (dsp, ':');
     if (dsp == NULL)
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     *dsp++ = (char) 0;

     /* int_name */
     int_name = dsp;
     dsp = strchr (dsp, ':');
     if (dsp == NULL)
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     *dsp++ = (char) 0;

     /* reg_num */
     reg = (int) strtol (dsp, &dsp, 0);
     if (*dsp++ != ':')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     if (reg < 0 && reg != -1)
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     if (reg >= 0)
       hwcdef[idx].reg_num = reg;

     /* val */
     interval = (int) strtol (dsp, &dsp, 0);
     if (*dsp++ != ':')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     if (interval < 0)
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     hwcdef[idx].val = interval;

     /* min_time */
     if (*dsp == 'm')
       {
         long long tmp_ll = 0;
         dsp++;
         tmp_ll = strtoll (dsp, &dsp, 0);
         if (*dsp++ != ':')
           {
             err = HWCFUNCS_ERROR_HWCARGS;
             break;
           }
         if (tmp_ll < 0)
           {
             err = HWCFUNCS_ERROR_HWCARGS;
             break;
           }
         hwcdef[idx].min_time = tmp_ll;
       }
     else
       hwcdef[idx].min_time = 0;

     /* sort_order */
     sort_order = (int) strtoul (dsp, &dsp, 0);
     if (*dsp++ != ':')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     hwcdef[idx].sort_order = sort_order;

     /* timecvt */
     timecvt = (int) strtol (dsp, &dsp, 0);
     if (*dsp++ != ':')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     hwcdef[idx].timecvt = timecvt;

     /* memop */
     memop = (ABST_type) strtol (dsp, &dsp, 0);
     if (*dsp != 0 && *dsp++ != ',')
       {
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     hwcdef[idx].memop = memop;
     if (*name)
       hwcdef[idx].name = strdup (name);
     else
       hwcdef[idx].name = strdup (int_name);
     if (*int_name)
       hwcdef[idx].int_name = strdup (int_name);
     else
       hwcdef[idx].int_name = strdup (name);
     ctrdefprint (DBG_LT1, "hwcfuncs: process_data_descriptor", &hwcdef[idx]);
   }

 if (*dsp)
   err = HWCFUNCS_ERROR_HWCARGS;
 if (err != 0)
   logerr (GTXT ("Data descriptor syntax error near `%s'\n"), dsp);
 else
   hwcdef_cnt = idx;
 free (ds);
 return err;
}

/* initialize hwcdef[] based on user's counter definitions */
static int
process_hwcentrylist (const Hwcentry* entries[], unsigned numctrs)
{
 int err = 0;
 clear_hwcdefs ();
 if (numctrs > cpcN_npics)
   {
     logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
     return HWCFUNCS_ERROR_HWCARGS;
   }
 for (unsigned idx = 0; idx < numctrs; idx++)
   {
     Hwcentry *phwcdef = &hwcdef[idx];
     *phwcdef = *entries[idx];
     if (phwcdef->name)
       phwcdef->name = strdup (phwcdef->name);
     else
       phwcdef->name = "NULL";
     if (phwcdef->int_name)
       phwcdef->int_name = strdup (phwcdef->int_name);
     else
       phwcdef->int_name = "NULL";
     if (phwcdef->val < 0)
       {
         logerr (GTXT ("Negative interval specified for HW counter `%s'\n"), /*!*/
                 phwcdef->name);
         err = HWCFUNCS_ERROR_HWCARGS;
         break;
       }
     ctrdefprint (DBG_LT1, "hwcfuncs: process_hwcentrylist", phwcdef);
   }
 if (!err)
   hwcdef_cnt = numctrs;
 return err;
}

/* see hwcfuncs.h */
IS_GLOBAL void *
hwcfuncs_parse_attrs (const char *countername, hwcfuncs_attr_t attrs[],
                     unsigned max_attrs, uint_t *pnum_attrs, char**errstring)
{
 char *head = NULL;
 char *tail = NULL;
 uint_t nattrs = 0;
 char *counter_copy;
 int success = 0;
 char errbuf[512];
 errbuf[0] = 0;
 counter_copy = strdup (countername);

 /* advance pointer to first attribute */
 tail = strchr (counter_copy, HWCFUNCS_PARSE_ATTR);
 if (tail)
   *tail = 0;

 /* remove regno and value, if supplied */
 {
   char *tmp = strchr (counter_copy, HWCFUNCS_PARSE_REGNUM);
   if (tmp)
     *tmp = 0;
   tmp = strchr (counter_copy, HWCFUNCS_PARSE_VALUE);
   if (tmp)
     *tmp = 0;
 }

 while (tail)
   {
     char *pch;
     if (nattrs >= max_attrs)
       {
         snprintf (errbuf, sizeof (errbuf),
                   GTXT ("Too many attributes defined in `%s'"),
                   countername);
         goto mycpc2_parse_attrs_end;
       }
     /* get attribute name */
     head = tail + 1;
     tail = strchr (head, HWCFUNCS_PARSE_EQUAL);
     if (!tail)
       {
         snprintf (errbuf, sizeof (errbuf),
                   GTXT ("Missing value for attribute `%s' in `%s'"),
                   head, countername);
         goto mycpc2_parse_attrs_end;
       }
     *tail = 0; /* null terminate current component */
     attrs[nattrs].ca_name = head;

     /* get attribute value */
     head = tail + 1;
     tail = strchr (head, HWCFUNCS_PARSE_ATTR);
     if (tail)
       *tail = 0; /* null terminate current component */
     attrs[nattrs].ca_val = strtoull (head, &pch, 0);
     if (pch == head)
       {
         snprintf (errbuf, sizeof (errbuf),
                   GTXT ("Illegal value for attribute `%s' in `%s'"),
                   attrs[nattrs].ca_name, countername);
         goto mycpc2_parse_attrs_end;
       }
     TprintfT (DBG_LT0, "hwcfuncs: pic_: '%s', attribute[%u]"
               " '%s' = 0x%llx\n",
               counter_copy, nattrs, attrs[nattrs].ca_name,
               (long long unsigned int) attrs[nattrs].ca_val);

     nattrs++;
   }
 success = 1;

mycpc2_parse_attrs_end:
 *pnum_attrs = nattrs;
 if (success)
   {
     if (errstring)
       *errstring = NULL;
   }
 else
   {
     if (errstring)
       *errstring = strdup (errbuf);
     free (counter_copy);
     counter_copy = NULL;
   }
 return counter_copy;
}

IS_GLOBAL void
hwcfuncs_parse_ctr (const char *counter_def, int *pplus, char **pnameOnly,
                   char **pattrs, char **pregstr, regno_t *pregno)
{
 char *nameptr, *copy, *slash, *attr_delim;
 int plus;
 regno_t regno;
 nameptr = copy = strdup (counter_def);

 /* plus */
 plus = 0;
 if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK)
   {
     plus = 1;
     nameptr++;
   }
 else if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK_OFF)
   {
     plus = -1;
     nameptr++;
   }
 if (pplus)
   *pplus = plus;

 /* regno */
 regno = REGNO_ANY;
 if (pregstr)
   *pregstr = NULL;
 slash = strchr (nameptr, HWCFUNCS_PARSE_REGNUM);
 if (slash != NULL)
   {
     /* the remaining string should be a number > 0 */
     if (pregstr)
       *pregstr = strdup (slash);
     char *endchar = NULL;
     regno = (regno_t) strtol (slash + 1, &endchar, 0);
     if (*endchar != 0)
       regno = -2;
     if (*(slash + 1) == '-')
       regno = -2;
     /* terminate previous element up to slash */
     *slash = 0;
   }
 if (pregno)
   *pregno = regno;

 /* attrs */
 if (pattrs)
   *pattrs = NULL;
 attr_delim = strchr (nameptr, HWCFUNCS_PARSE_ATTR);
 if (attr_delim != NULL)
   {
     if (pattrs)
       *pattrs = strdup (attr_delim);
     /* terminate previous element up to attr_delim */
     *attr_delim++ = 0;
   }
 if (pnameOnly)
   *pnameOnly = strdup (nameptr);
 free (copy);
}

/* create counters */
IS_GLOBAL int
hwcfuncs_bind_descriptor (const char *defstring)
{
 int err = process_data_descriptor (defstring);
 if (err)
   {
     TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_descriptor failed\n");
     return err;
   }
 err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
 return err;
}

/* see hwcfuncs.h */
IS_GLOBAL int
hwcfuncs_bind_hwcentry (const Hwcentry* entries[], unsigned numctrs)
{
 int err = -1;
 err = process_hwcentrylist (entries, numctrs);
 if (err)
   {
     TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_hwcentry\n");
     return err;
   }
 err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
 return err;
}

/* see hwcfuncs.h */
IS_GLOBAL Hwcentry **
hwcfuncs_get_ctrs (unsigned *defcnt)
{
 if (defcnt)
   *defcnt = hwcdef_cnt;
 return hwctable;
}

/* return 1 if <regno> is in Hwcentry's list */
IS_GLOBAL int
regno_is_valid (const Hwcentry * pctr, regno_t regno)
{
 regno_t *reg_list = pctr->reg_list;
 if (REG_LIST_IS_EMPTY (reg_list))
   return 0;
 if (regno == REGNO_ANY)   /* wildcard */
   return 1;
 for (int ii = 0; ii < MAX_PICS; ii++)
   {
     regno_t tmp = reg_list[ii];
     if (REG_LIST_EOL (tmp))   /* end of list */
       break;
     if (tmp == regno)     /* is in list */
       return 1;
   }
 return 0;
}

/* supplied by hwcdrv_api drivers */
IS_GLOBAL int
hwcfuncs_assign_regnos (Hwcentry* entries[],
                       unsigned numctrs)
{
 if (numctrs > cpcN_npics)
   {
     logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
     return HWCFUNCS_ERROR_HWCARGS;
   }
 return hwcdrv_driver->hwcdrv_assign_regnos (entries, numctrs);
}

extern hwcdrv_api_t hwcdrv_pcl_api;
static int hwcdrv_driver_inited = 0;

hwcdrv_api_t *
get_hwcdrv ()
{
 if (hwcdrv_driver_inited)
   return hwcdrv_driver;
 hwcdrv_driver_inited = 1;
 cpcN_npics = 0;
 for (int i = 0; i < MAX_PICS; i++)
   hwctable[i] = &hwcdef[i];
 hwcdrv_driver = &hwcdrv_pcl_api;
 hwcdrv_driver->hwcdrv_init_status = hwcdrv_driver->hwcdrv_init (NULL, NULL);
 if (hwcdrv_driver->hwcdrv_init_status == 0)
   {
     hwcdrv_driver->hwcdrv_get_info (NULL, NULL, &cpcN_npics, NULL, NULL);
     return hwcdrv_driver;
   }
 hwcdrv_driver = &hwcdrv_default;
 return hwcdrv_driver;
}