util-linux/sys-utils/lscpu-cputype.c

667 lines
16 KiB
C
Raw Normal View History

#include <sys/utsname.h>
#include <sys/personality.h>
#include "lscpu-api.h"
UL_DEBUG_DEFINE_MASK(lscpu);
UL_DEBUG_DEFINE_MASKNAMES(lscpu) = UL_DEBUG_EMPTY_MASKNAMES;
static void lscpu_init_debug(void)
{
__UL_INIT_DEBUG_FROM_ENV(lscpu, LSCPU_DEBUG_, 0, LSCPU_DEBUG);
}
static void context_init_paths(struct lscpu_cxt *cxt)
{
DBG(MISC, ul_debugobj(cxt, "initialize paths"));
ul_path_init_debug();
/* /sys/devices/system/cpu */
cxt->syscpu = ul_new_path(_PATH_SYS_CPU);
if (!cxt->syscpu)
err(EXIT_FAILURE, _("failed to initialize CPUs sysfs handler"));
if (cxt->prefix)
ul_path_set_prefix(cxt->syscpu, cxt->prefix);
/* /proc */
cxt->procfs = ul_new_path("/proc");
if (!cxt->procfs)
err(EXIT_FAILURE, _("failed to initialize procfs handler"));
if (cxt->prefix)
ul_path_set_prefix(cxt->procfs, cxt->prefix);
}
/* Lookup a pattern and get the value for format "<pattern> : <key>"
*/
static int lookup(char *line, char *pattern, char **value)
{
char *p, *v;
int len = strlen(pattern);
/* don't re-fill already found tags, first one wins */
if (!*line || *value)
return 0;
/* pattern */
if (strncmp(line, pattern, len))
return 0;
/* white spaces */
for (p = line + len; isspace(*p); p++);
/* separator */
if (*p != ':')
return 0;
/* white spaces */
for (++p; isspace(*p); p++);
/* value */
if (!*p)
return 0;
v = p;
/* end of value */
len = strlen(line) - 1;
for (p = line + len; isspace(*(p-1)); p--);
*p = '\0';
*value = xstrdup(v);
return 1;
}
struct lscpu_cputype *lscpu_new_cputype(void)
{
struct lscpu_cputype *ct;
ct = xcalloc(1, sizeof(struct lscpu_cputype));
ct->refcount = 1;
DBG(TYPE, ul_debugobj(ct, "alloc"));
return ct;
}
void lscpu_ref_cputype(struct lscpu_cputype *ct)
{
if (ct)
ct->refcount++;
}
void lscpu_unref_cputype(struct lscpu_cputype *ct)
{
if (!ct)
return;
if (--ct->refcount <= 0) {
DBG(TYPE, ul_debugobj(ct, " freeing"));
free(ct->vendor);
free(ct->machinetype); /* s390 */
free(ct->family);
free(ct->model);
free(ct->modelname);
free(ct->revision); /* alternative for model (ppc) */
free(ct->virtflag); /* virtualization flag (vmx, svm) */
free(ct->hypervisor); /* hypervisor software */
free(ct->stepping);
free(ct->bogomips);
free(ct->flags);
free(ct->mtid); /* maximum thread id (s390) */
free(ct->addrsz); /* address sizes */
free(ct);
}
}
struct lscpu_cputype *lscpu_cputype_get_default(struct lscpu_cxt *cxt)
{
return cxt->cputypes ? cxt->cputypes[0] : NULL;
}
#define match(astr, bstr) \
((!astr && !bstr) || (astr && bstr && strcmp(astr, bstr) == 0))
struct lscpu_cputype *lscpu_add_cputype(struct lscpu_cxt *cxt, struct lscpu_cputype *ct)
{
size_t i;
/* ignore if already in the context */
for (i = 0; i < cxt->ncputypes; i++) {
struct lscpu_cputype *x = cxt->cputypes[i];
if (match(x->vendor, ct->vendor) &&
match(x->model, ct->model) &&
match(x->modelname, ct->modelname) &&
match(x->stepping, ct->stepping)) {
DBG(TYPE, ul_debugobj(x, "reuse"));
return x;
}
}
DBG(TYPE, ul_debugobj(ct, "add new"));
cxt->cputypes = xrealloc(cxt->cputypes, (cxt->ncputypes + 1)
* sizeof(struct lscpu_cputype *));
cxt->cputypes[cxt->ncputypes] = ct;
cxt->ncputypes++;
lscpu_ref_cputype(ct);
/* first type -- use it for all CPUs */
if (cxt->ncputypes == 1)
lscpu_cpus_apply_type(cxt, ct);
return ct;
}
static void lscpu_merge_cputype(struct lscpu_cputype *a, struct lscpu_cputype *b)
{
if (!a->vendor && b->vendor)
a->vendor = xstrdup(b->vendor);
if (!a->machinetype && b->machinetype)
a->machinetype = xstrdup(b->machinetype);
if (!a->family && b->family)
a->family = xstrdup(b->family);
if (!a->model && b->model)
a->model = xstrdup(b->model);
if (!a->modelname && b->modelname)
a->modelname = xstrdup(b->modelname);
if (!a->revision && b->revision)
a->revision = xstrdup(b->revision);
if (!a->virtflag && b->virtflag)
a->virtflag = xstrdup(b->virtflag);
if (!a->hypervisor && b->hypervisor)
a->hypervisor = xstrdup(b->hypervisor);
if (!a->stepping && b->stepping)
a->stepping = xstrdup(b->stepping);
if (!a->bogomips && b->bogomips)
a->bogomips = xstrdup(b->bogomips);
if (!a->flags && b->flags)
a->flags = xstrdup(b->flags);
if (!a->mtid && b->mtid)
a->mtid = xstrdup(b->mtid);
if (!a->addrsz && b->addrsz)
a->addrsz = xstrdup(b->addrsz);
}
/* Describes /proc/cpuinfo fields */
struct cpuinfo_pattern {
int id; /* field ID */
const char *pattern; /* field name as used in /proc/cpuinfo */
size_t offset; /* offset in lscpu_cputype or lscpu_cpu struct */
};
/* field identifiers (field name may be different on different archs) */
enum {
PAT_ADDRESS_SIZES,
PAT_BOGOMIPS,
PAT_CPU,
PAT_FAMILY,
PAT_FEATURES,
PAT_FLAGS,
PAT_IMPLEMENTER,
PAT_MAX_THREAD_ID,
PAT_MHZ,
PAT_MHZ_DYNAMIC,
PAT_MHZ_STATIC,
PAT_MODEL,
PAT_MODEL_NAME,
PAT_PART,
PAT_PROCESSOR,
PAT_REVISION,
PAT_STEPPING,
PAT_TYPE,
PAT_VARIANT,
PAT_VENDOR,
};
/*
* /proc/cpuinfo to lscpu_cputype conversion
*/
#define DEF_PAT_CPUTYPE(_str, _id, _member) \
{ \
.id = (_id), \
.pattern = (_str), \
.offset = offsetof(struct lscpu_cputype, _member), \
}
static const struct cpuinfo_pattern type_patterns[] =
{
/* Sort by fields name! */
DEF_PAT_CPUTYPE( "BogoMIPS", PAT_BOGOMIPS, bogomips), /* aarch64 */
DEF_PAT_CPUTYPE( "CPU implementer", PAT_IMPLEMENTER,vendor), /* ARM and aarch64 */
DEF_PAT_CPUTYPE( "CPU part", PAT_PART, model), /* ARM and aarch64 */
DEF_PAT_CPUTYPE( "CPU revision", PAT_REVISION, revision), /* aarch64 */
DEF_PAT_CPUTYPE( "CPU variant", PAT_VARIANT, stepping), /* aarch64 */
DEF_PAT_CPUTYPE( "Features", PAT_FEATURES, flags), /* aarch64 */
DEF_PAT_CPUTYPE( "address sizes", PAT_ADDRESS_SIZES, addrsz),/* x86 */
DEF_PAT_CPUTYPE( "bogomips", PAT_BOGOMIPS, bogomips),
DEF_PAT_CPUTYPE( "bogomips per cpu", PAT_BOGOMIPS, bogomips), /* s390 */
DEF_PAT_CPUTYPE( "cpu family", PAT_FAMILY, family),
DEF_PAT_CPUTYPE( "cpu", PAT_CPU, modelname), /* ppc, sparc */
DEF_PAT_CPUTYPE( "family", PAT_FAMILY, family),
DEF_PAT_CPUTYPE( "features", PAT_FEATURES, flags), /* s390 */
DEF_PAT_CPUTYPE( "flags", PAT_FLAGS, flags), /* x86 */
DEF_PAT_CPUTYPE( "max thread id", PAT_MAX_THREAD_ID, mtid), /* s390 */
DEF_PAT_CPUTYPE( "model name", PAT_MODEL_NAME, modelname),
DEF_PAT_CPUTYPE( "model", PAT_MODEL, model),
DEF_PAT_CPUTYPE( "revision", PAT_REVISION, revision),
DEF_PAT_CPUTYPE( "stepping", PAT_STEPPING, stepping),
DEF_PAT_CPUTYPE( "type", PAT_TYPE, flags), /* sparc64 */
DEF_PAT_CPUTYPE( "vendor", PAT_VENDOR, vendor),
DEF_PAT_CPUTYPE( "vendor_id", PAT_VENDOR, vendor), /* s390 */
};
/*
* /proc/cpuinfo to lscpu_cpu conversion
*/
#define DEF_PAT_CPU(_str, _id, _member) \
{ \
.id = (_id), \
.pattern = (_str), \
.offset = offsetof(struct lscpu_cpu, _member), \
}
static const struct cpuinfo_pattern cpu_patterns[] =
{
/* Sort by fields name! */
DEF_PAT_CPU( "cpu MHz dynamic", PAT_MHZ_DYNAMIC,dynamic_mhz), /* s390 */
DEF_PAT_CPU( "cpu MHz static", PAT_MHZ_STATIC, static_mhz), /* s390 */
DEF_PAT_CPU( "cpu MHz", PAT_MHZ, mhz),
DEF_PAT_CPU( "cpu number", PAT_PROCESSOR, logical_id), /* s390 */
DEF_PAT_CPU( "processor", PAT_PROCESSOR, logical_id),
};
#define CPUTYPE_PATTERN_BUFSZ 32
static int cmp_pattern(const void *a0, const void *b0)
{
const struct cpuinfo_pattern
*a = (const struct cpuinfo_pattern *) a0,
*b = (const struct cpuinfo_pattern *) b0;
return strcmp(a->pattern, b->pattern);
}
static int cpuinfo_parse_line( struct lscpu_cputype **ct,
struct lscpu_cpu **cpu,
const char *str)
{
struct cpuinfo_pattern key, *pat;
const char *p, *v;
char buf[CPUTYPE_PATTERN_BUFSZ] = { 0 }, **data;
void *stru = NULL;
DBG(GATHER, ul_debug("parse \"%s\"", str));
if (!str || !*str)
return -EINVAL;
p = skip_blank(str);
if (!p || !*p)
return -EINVAL;
v = strchr(p, ':');
if (!v || !*v)
return -EINVAL;
/* prepare name of the field */
xstrncpy(buf, p, sizeof(buf));
buf[v - p] = '\0';
v++;
rtrim_whitespace((unsigned char *)buf);
/* search in cpu-types patterns */
key.pattern = buf;
pat = bsearch(&key, type_patterns,
ARRAY_SIZE(type_patterns),
sizeof(struct cpuinfo_pattern),
cmp_pattern);
if (pat) {
/* CPU type */
if (!*ct)
*ct = lscpu_new_cputype();
stru = *ct;
} else {
/* search in cpu patterns */
pat = bsearch(&key, cpu_patterns,
ARRAY_SIZE(cpu_patterns),
sizeof(struct cpuinfo_pattern),
cmp_pattern);
if (pat) {
if (!*cpu)
*cpu = lscpu_new_cpu();
stru = *cpu;
}
}
if (!stru) {
DBG(GATHER, ul_debug("'%s' not found", buf));
return 1;
}
/* prepare value */
v = skip_space(v);
if (!v || !*v)
return -EINVAL;
/* copy value to struct */
switch (pat->id) {
case PAT_PROCESSOR:
(*cpu)->logical_id = atoi(v);
break;
default:
/* set value as a string and cleanup */
strdup_to_offset(stru, pat->offset, v);
data = (char **) ((char *) stru + pat->offset);
rtrim_whitespace((unsigned char *) *data);
break;
}
return 0;
}
int lscpu_read_cpuinfo(struct lscpu_cxt *cxt)
{
struct lscpu_cputype *type = NULL;
struct lscpu_cpu *cpu = NULL;
FILE *fp;
char buf[BUFSIZ];
DBG(GATHER, ul_debugobj(cxt, "reading cpuinfo"));
fp = ul_path_fopen(cxt->procfs, "r", "cpuinfo");
if (!fp)
err(EXIT_FAILURE, _("cannot open %s"), "/proc/cpuinfo");
do {
const char *p = NULL;
if (fgets(buf, sizeof(buf), fp) != NULL)
p = skip_space(buf);
if (p == NULL || (*buf && !*p)) {
if (cpu)
lscpu_add_cpu(cxt, cpu, type);
else if (type) {
/* Generic non-cpu data. For some architectures
* cpuinfo contains description block (at the
* beginning of the file (IBM s390) or at the
* end of the file (IBM POWER). The block is
* global for all CPUs.
*/
if (cxt->ncputypes == 1) {
/* The type already exist, merge it. For example on POWER
* CPU list contains "cpu:" line with architecture and
* global information at the end of the file */
struct lscpu_cputype *dflt = lscpu_cputype_get_default(cxt);
if (dflt)
lscpu_merge_cputype(dflt, type);
} else
lscpu_add_cputype(cxt, type);
}
lscpu_unref_cpu(cpu);
lscpu_unref_cputype(type);
cpu = NULL, type = NULL;
if (p == NULL)
break; /* fgets() returns nothing; EOF */
} else {
rtrim_whitespace((unsigned char *) buf);
cpuinfo_parse_line(&type, &cpu, p);
}
} while (1);
lscpu_unref_cpu(cpu);
lscpu_unref_cputype(type);
fclose(fp);
return 0;
}
int lscpu_read_architecture(struct lscpu_cxt *cxt)
{
struct utsname utsbuf;
struct lscpu_arch *ar;
struct lscpu_cputype *ct;
assert(cxt);
assert(!cxt->arch);
DBG(GATHER, ul_debugobj(cxt, "reading architecture"));
if (uname(&utsbuf) == -1)
err(EXIT_FAILURE, _("error: uname failed"));
ar = cxt->arch = xcalloc(1, sizeof(*cxt->arch));
ar->name = xstrdup(utsbuf.machine);
if (cxt->noalive)
/* reading info from any /{sys,proc} dump, don't mix it with
* information about our real CPU */
;
else {
#if defined(__alpha__) || defined(__ia64__)
ar->bit64 = 1; /* 64bit platforms only */
#endif
/* platforms with 64bit flag in /proc/cpuinfo, define
* 32bit default here */
#if defined(__i386__) || defined(__x86_64__) || \
defined(__s390x__) || defined(__s390__) || defined(__sparc_v9__)
ar->bit32 = 1;
#endif
#if defined(__aarch64__)
{
/* personality() is the most reliable way (since 4.7)
* to determine aarch32 support */
int pers = personality(PER_LINUX32);
if (pers != -1) {
personality(pers);
ar->bit32 = 1;
}
ar->bit64 = 1;
}
#endif
}
ct = lscpu_cputype_get_default(cxt);
if (ct && ct->flags) {
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), " %s ", ct->flags);
if (strstr(buf, " lm "))
ar->bit32 = 1, ar->bit64 = 1; /* x86_64 */
if (strstr(buf, " zarch "))
ar->bit32 = 1, ar->bit64 = 1; /* s390x */
if (strstr(buf, " sun4v ") || strstr(buf, " sun4u "))
ar->bit32 = 1, ar->bit64 = 1; /* sparc64 */
}
if (ar->name && !cxt->noalive) {
if (strcmp(ar->name, "ppc64") == 0)
ar->bit32 = 1, ar->bit64 = 1;
else if (strcmp(ar->name, "ppc") == 0)
ar->bit32 = 1;
}
return 0;
}
int lscpu_read_cpulists(struct lscpu_cxt *cxt)
{
size_t maxn;
size_t setsize;
cpu_set_t *cpuset = NULL;
assert(cxt);
DBG(GATHER, ul_debugobj(cxt, "reading cpulists"));
if (ul_path_read_s32(cxt->syscpu, &cxt->maxcpus, "kernel_max") == 0)
/* note that kernel_max is maximum index [NR_CPUS-1] */
cxt->maxcpus += 1;
else if (!cxt->noalive)
/* the root is '/' so we are working with data from the current kernel */
cxt->maxcpus = get_max_number_of_cpus();
if (cxt->maxcpus <= 0)
/* error or we are reading some /sys snapshot instead of the
* real /sys, let's use any crazy number... */
cxt->maxcpus = 2048;
maxn = cxt->maxcpus;
setsize = CPU_ALLOC_SIZE(maxn);
if (ul_path_readf_cpulist(cxt->syscpu, &cpuset, maxn, "possible") == 0) {
size_t num, idx;
cxt->ncpuspos = CPU_COUNT_S(setsize, cpuset);
cxt->idx2cpunum = xcalloc(cxt->ncpuspos, sizeof(int));
for (num = 0, idx = 0; num < maxn; num++) {
if (CPU_ISSET_S(num, setsize, cpuset))
cxt->idx2cpunum[idx++] = num;
}
cpuset_free(cpuset);
cpuset = NULL;
} else
err(EXIT_FAILURE, _("failed to determine number of CPUs: %s"),
_PATH_SYS_CPU "/possible");
/* get mask for present CPUs */
if (ul_path_readf_cpulist(cxt->syscpu, &cxt->present, maxn, "present") == 0)
cxt->npresents = CPU_COUNT_S(setsize, cxt->present);
/* get mask for online CPUs */
if (ul_path_readf_cpulist(cxt->syscpu, &cxt->online, maxn, "online") == 0)
cxt->nonlines = CPU_COUNT_S(setsize, cxt->online);
return 0;
}
#if defined(HAVE_LIBRTAS)
# define PROCESSOR_MODULE_INFO 43
static int strbe16toh(const char *buf, int offset)
{
return (buf[offset] << 8) + buf[offset+1];
}
#endif
/* some extra information for the default CPU type */
int lscpu_read_extra(struct lscpu_cxt *cxt)
{
FILE *f;
char buf[BUFSIZ];
struct lscpu_cputype *ct;
assert(cxt);
ct = lscpu_cputype_get_default(cxt);
if (!ct)
return -EINVAL;
/* get dispatching mode */
if (ul_path_read_s32(cxt->syscpu, &ct->dispatching, "dispatching") != 0)
ct->dispatching = -1;
/* get cpufreq boost mode */
if (ul_path_read_s32(cxt->syscpu, &ct->freqboost, "cpufreq/boost") != 0)
ct->freqboost = -1;
if ((f = ul_path_fopen(cxt->procfs, "r", "sysinfo"))) {
while (fgets(buf, sizeof(buf), f) != NULL) {
if (lookup(buf, "Type", &ct->machinetype))
break;
}
fclose(f);
}
#if defined(HAVE_LIBRTAS)
/* Get PowerPC speficic info */
if (!cxt->noalive) {
int rc, len, ntypes;
ct->physsockets = ct->physchips = ct->physcoresperchip = 0;
rc = rtas_get_sysparm(PROCESSOR_MODULE_INFO, sizeof(buf), buf);
if (rc < 0)
goto nortas;
len = strbe16toh(buf, 0);
if (len < 8)
goto nortas;
ntypes = strbe16toh(buf, 2);
assert(ntypes <= 1);
if (!ntypes)
goto nortas;
desc->physsockets = strbe16toh(buf, 4);
desc->physchips = strbe16toh(buf, 6);
desc->physcoresperchip = strbe16toh(buf, 8);
nortas:
}
#endif
return 0;
}
#ifdef TEST_PROGRAM_CPUTYPE
/* TODO: move to lscpu.c */
struct lscpu_cxt *lscpu_new_context(void)
{
return xcalloc(1, sizeof(struct lscpu_cxt));
}
void lscpu_free_context(struct lscpu_cxt *cxt)
{
size_t i;
if (!cxt)
return;
DBG(MISC, ul_debugobj(cxt, "freeing context"));
DBG(MISC, ul_debugobj(cxt, " de-initialize paths"));
ul_unref_path(cxt->syscpu);
ul_unref_path(cxt->procfs);
DBG(MISC, ul_debugobj(cxt, " freeing cpus"));
for (i = 0; i < cxt->ncpus; i++)
lscpu_unref_cpu(cxt->cpus[i]);
DBG(MISC, ul_debugobj(cxt, " freeing types"));
for (i = 0; i < cxt->ncputypes; i++)
lscpu_unref_cputype(cxt->cputypes[i]);
free(cxt->idx2cpunum);
free(cxt->present);
free(cxt->online);
if (cxt->arch)
free(cxt->arch->name);
free(cxt->arch);
free(cxt->cputypes);
free(cxt->cpus);
free(cxt);
}
int main(int argc, char **argv)
{
struct lscpu_cxt *cxt;
cxt = lscpu_new_context();
if (argc == 3 && strcmp(argv[1], "--prefix") == 0) {
cxt->prefix = argv[2];
cxt->noalive = 1;
}
lscpu_init_debug();
context_init_paths(cxt);
lscpu_read_cpuinfo(cxt);
lscpu_read_architecture(cxt);
lscpu_read_cpulists(cxt);
lscpu_read_extra(cxt);
lscpu_free_context(cxt);
return EXIT_SUCCESS;
}
#endif /* TEST_PROGRAM_CPUTYPES */