blob: e6448a9e0626848a15f3604e6c63b5dc437d19ec [file] [log] [blame]
/* 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, Hwcentry *hwcdef)
{
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\n",
hdr, phwcdef->name, phwcdef->int_name, phwcdef->reg_num,
phwcdef->timecvt, phwcdef->memop, phwcdef->val,
phwcdef->sort_order);
}
/*---------------------------------------------------------------------------*/
/* 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 */
static 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;
}
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;
}