glibc/sysdeps/x86/dl-cacheinfo.h
H.J. Lu 0f09154c64 x86: Initialize CPU info via IFUNC relocation [BZ 26203]
X86 CPU features in ld.so are initialized by init_cpu_features, which is
invoked by DL_PLATFORM_INIT from _dl_sysdep_start.  But when ld.so is
loaded by static executable, DL_PLATFORM_INIT is never called.  Also
x86 cache info in libc.o and libc.a is initialized by a constructor
which may be called too late.  Since some fields in _rtld_global_ro
in ld.so are initialized by dynamic relocation, we can also initialize
x86 CPU features in _rtld_global_ro in ld.so and cache info in libc.so
by initializing dummy function pointers in ld.so and libc.so via IFUNC
relocation.

Key points:

1. IFUNC is always supported, independent of --enable-multi-arch or
--disable-multi-arch.  Linker generates IFUNC relocations from input
IFUNC objects and ld.so performs IFUNC relocations.
2. There are no IFUNC dependencies in ld.so before dynamic relocation
have been performed,
3. The x86 CPU features in ld.so is initialized by DL_PLATFORM_INIT
in dynamic executable and by IFUNC relocation in dlopen in static
executable.
4. The x86 cache info in libc.o is initialized by IFUNC relocation.
5. In libc.a, both x86 CPU features and cache info are initialized from
ARCH_INIT_CPU_FEATURES, not by IFUNC relocation, before __libc_early_init
is called.

Note: _dl_x86_init_cpu_features can be called more than once from
DL_PLATFORM_INIT and during relocation in ld.so.
2020-10-16 16:17:53 -07:00

479 lines
13 KiB
C

/* Initialize x86 cache info.
Copyright (C) 2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
static const struct intel_02_cache_info
{
unsigned char idx;
unsigned char assoc;
unsigned char linesize;
unsigned char rel_name;
unsigned int size;
} intel_02_known [] =
{
#define M(sc) ((sc) - _SC_LEVEL1_ICACHE_SIZE)
{ 0x06, 4, 32, M(_SC_LEVEL1_ICACHE_SIZE), 8192 },
{ 0x08, 4, 32, M(_SC_LEVEL1_ICACHE_SIZE), 16384 },
{ 0x09, 4, 32, M(_SC_LEVEL1_ICACHE_SIZE), 32768 },
{ 0x0a, 2, 32, M(_SC_LEVEL1_DCACHE_SIZE), 8192 },
{ 0x0c, 4, 32, M(_SC_LEVEL1_DCACHE_SIZE), 16384 },
{ 0x0d, 4, 64, M(_SC_LEVEL1_DCACHE_SIZE), 16384 },
{ 0x0e, 6, 64, M(_SC_LEVEL1_DCACHE_SIZE), 24576 },
{ 0x21, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 262144 },
{ 0x22, 4, 64, M(_SC_LEVEL3_CACHE_SIZE), 524288 },
{ 0x23, 8, 64, M(_SC_LEVEL3_CACHE_SIZE), 1048576 },
{ 0x25, 8, 64, M(_SC_LEVEL3_CACHE_SIZE), 2097152 },
{ 0x29, 8, 64, M(_SC_LEVEL3_CACHE_SIZE), 4194304 },
{ 0x2c, 8, 64, M(_SC_LEVEL1_DCACHE_SIZE), 32768 },
{ 0x30, 8, 64, M(_SC_LEVEL1_ICACHE_SIZE), 32768 },
{ 0x39, 4, 64, M(_SC_LEVEL2_CACHE_SIZE), 131072 },
{ 0x3a, 6, 64, M(_SC_LEVEL2_CACHE_SIZE), 196608 },
{ 0x3b, 2, 64, M(_SC_LEVEL2_CACHE_SIZE), 131072 },
{ 0x3c, 4, 64, M(_SC_LEVEL2_CACHE_SIZE), 262144 },
{ 0x3d, 6, 64, M(_SC_LEVEL2_CACHE_SIZE), 393216 },
{ 0x3e, 4, 64, M(_SC_LEVEL2_CACHE_SIZE), 524288 },
{ 0x3f, 2, 64, M(_SC_LEVEL2_CACHE_SIZE), 262144 },
{ 0x41, 4, 32, M(_SC_LEVEL2_CACHE_SIZE), 131072 },
{ 0x42, 4, 32, M(_SC_LEVEL2_CACHE_SIZE), 262144 },
{ 0x43, 4, 32, M(_SC_LEVEL2_CACHE_SIZE), 524288 },
{ 0x44, 4, 32, M(_SC_LEVEL2_CACHE_SIZE), 1048576 },
{ 0x45, 4, 32, M(_SC_LEVEL2_CACHE_SIZE), 2097152 },
{ 0x46, 4, 64, M(_SC_LEVEL3_CACHE_SIZE), 4194304 },
{ 0x47, 8, 64, M(_SC_LEVEL3_CACHE_SIZE), 8388608 },
{ 0x48, 12, 64, M(_SC_LEVEL2_CACHE_SIZE), 3145728 },
{ 0x49, 16, 64, M(_SC_LEVEL2_CACHE_SIZE), 4194304 },
{ 0x4a, 12, 64, M(_SC_LEVEL3_CACHE_SIZE), 6291456 },
{ 0x4b, 16, 64, M(_SC_LEVEL3_CACHE_SIZE), 8388608 },
{ 0x4c, 12, 64, M(_SC_LEVEL3_CACHE_SIZE), 12582912 },
{ 0x4d, 16, 64, M(_SC_LEVEL3_CACHE_SIZE), 16777216 },
{ 0x4e, 24, 64, M(_SC_LEVEL2_CACHE_SIZE), 6291456 },
{ 0x60, 8, 64, M(_SC_LEVEL1_DCACHE_SIZE), 16384 },
{ 0x66, 4, 64, M(_SC_LEVEL1_DCACHE_SIZE), 8192 },
{ 0x67, 4, 64, M(_SC_LEVEL1_DCACHE_SIZE), 16384 },
{ 0x68, 4, 64, M(_SC_LEVEL1_DCACHE_SIZE), 32768 },
{ 0x78, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 1048576 },
{ 0x79, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 131072 },
{ 0x7a, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 262144 },
{ 0x7b, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 524288 },
{ 0x7c, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 1048576 },
{ 0x7d, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 2097152 },
{ 0x7f, 2, 64, M(_SC_LEVEL2_CACHE_SIZE), 524288 },
{ 0x80, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 524288 },
{ 0x82, 8, 32, M(_SC_LEVEL2_CACHE_SIZE), 262144 },
{ 0x83, 8, 32, M(_SC_LEVEL2_CACHE_SIZE), 524288 },
{ 0x84, 8, 32, M(_SC_LEVEL2_CACHE_SIZE), 1048576 },
{ 0x85, 8, 32, M(_SC_LEVEL2_CACHE_SIZE), 2097152 },
{ 0x86, 4, 64, M(_SC_LEVEL2_CACHE_SIZE), 524288 },
{ 0x87, 8, 64, M(_SC_LEVEL2_CACHE_SIZE), 1048576 },
{ 0xd0, 4, 64, M(_SC_LEVEL3_CACHE_SIZE), 524288 },
{ 0xd1, 4, 64, M(_SC_LEVEL3_CACHE_SIZE), 1048576 },
{ 0xd2, 4, 64, M(_SC_LEVEL3_CACHE_SIZE), 2097152 },
{ 0xd6, 8, 64, M(_SC_LEVEL3_CACHE_SIZE), 1048576 },
{ 0xd7, 8, 64, M(_SC_LEVEL3_CACHE_SIZE), 2097152 },
{ 0xd8, 8, 64, M(_SC_LEVEL3_CACHE_SIZE), 4194304 },
{ 0xdc, 12, 64, M(_SC_LEVEL3_CACHE_SIZE), 2097152 },
{ 0xdd, 12, 64, M(_SC_LEVEL3_CACHE_SIZE), 4194304 },
{ 0xde, 12, 64, M(_SC_LEVEL3_CACHE_SIZE), 8388608 },
{ 0xe2, 16, 64, M(_SC_LEVEL3_CACHE_SIZE), 2097152 },
{ 0xe3, 16, 64, M(_SC_LEVEL3_CACHE_SIZE), 4194304 },
{ 0xe4, 16, 64, M(_SC_LEVEL3_CACHE_SIZE), 8388608 },
{ 0xea, 24, 64, M(_SC_LEVEL3_CACHE_SIZE), 12582912 },
{ 0xeb, 24, 64, M(_SC_LEVEL3_CACHE_SIZE), 18874368 },
{ 0xec, 24, 64, M(_SC_LEVEL3_CACHE_SIZE), 25165824 },
};
#define nintel_02_known (sizeof (intel_02_known) / sizeof (intel_02_known [0]))
static int
intel_02_known_compare (const void *p1, const void *p2)
{
const struct intel_02_cache_info *i1;
const struct intel_02_cache_info *i2;
i1 = (const struct intel_02_cache_info *) p1;
i2 = (const struct intel_02_cache_info *) p2;
if (i1->idx == i2->idx)
return 0;
return i1->idx < i2->idx ? -1 : 1;
}
static long int
__attribute__ ((noinline))
intel_check_word (int name, unsigned int value, bool *has_level_2,
bool *no_level_2_or_3,
const struct cpu_features *cpu_features)
{
if ((value & 0x80000000) != 0)
/* The register value is reserved. */
return 0;
/* Fold the name. The _SC_ constants are always in the order SIZE,
ASSOC, LINESIZE. */
int folded_rel_name = (M(name) / 3) * 3;
while (value != 0)
{
unsigned int byte = value & 0xff;
if (byte == 0x40)
{
*no_level_2_or_3 = true;
if (folded_rel_name == M(_SC_LEVEL3_CACHE_SIZE))
/* No need to look further. */
break;
}
else if (byte == 0xff)
{
/* CPUID leaf 0x4 contains all the information. We need to
iterate over it. */
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int round = 0;
while (1)
{
__cpuid_count (4, round, eax, ebx, ecx, edx);
enum { null = 0, data = 1, inst = 2, uni = 3 } type = eax & 0x1f;
if (type == null)
/* That was the end. */
break;
unsigned int level = (eax >> 5) & 0x7;
if ((level == 1 && type == data
&& folded_rel_name == M(_SC_LEVEL1_DCACHE_SIZE))
|| (level == 1 && type == inst
&& folded_rel_name == M(_SC_LEVEL1_ICACHE_SIZE))
|| (level == 2 && folded_rel_name == M(_SC_LEVEL2_CACHE_SIZE))
|| (level == 3 && folded_rel_name == M(_SC_LEVEL3_CACHE_SIZE))
|| (level == 4 && folded_rel_name == M(_SC_LEVEL4_CACHE_SIZE)))
{
unsigned int offset = M(name) - folded_rel_name;
if (offset == 0)
/* Cache size. */
return (((ebx >> 22) + 1)
* (((ebx >> 12) & 0x3ff) + 1)
* ((ebx & 0xfff) + 1)
* (ecx + 1));
if (offset == 1)
return (ebx >> 22) + 1;
assert (offset == 2);
return (ebx & 0xfff) + 1;
}
++round;
}
/* There is no other cache information anywhere else. */
break;
}
else
{
if (byte == 0x49 && folded_rel_name == M(_SC_LEVEL3_CACHE_SIZE))
{
/* Intel reused this value. For family 15, model 6 it
specifies the 3rd level cache. Otherwise the 2nd
level cache. */
unsigned int family = cpu_features->basic.family;
unsigned int model = cpu_features->basic.model;
if (family == 15 && model == 6)
{
/* The level 3 cache is encoded for this model like
the level 2 cache is for other models. Pretend
the caller asked for the level 2 cache. */
name = (_SC_LEVEL2_CACHE_SIZE
+ (name - _SC_LEVEL3_CACHE_SIZE));
folded_rel_name = M(_SC_LEVEL2_CACHE_SIZE);
}
}
struct intel_02_cache_info *found;
struct intel_02_cache_info search;
search.idx = byte;
found = bsearch (&search, intel_02_known, nintel_02_known,
sizeof (intel_02_known[0]), intel_02_known_compare);
if (found != NULL)
{
if (found->rel_name == folded_rel_name)
{
unsigned int offset = M(name) - folded_rel_name;
if (offset == 0)
/* Cache size. */
return found->size;
if (offset == 1)
return found->assoc;
assert (offset == 2);
return found->linesize;
}
if (found->rel_name == M(_SC_LEVEL2_CACHE_SIZE))
*has_level_2 = true;
}
}
/* Next byte for the next round. */
value >>= 8;
}
/* Nothing found. */
return 0;
}
static long int __attribute__ ((noinline))
handle_intel (int name, const struct cpu_features *cpu_features)
{
unsigned int maxidx = cpu_features->basic.max_cpuid;
/* Return -1 for older CPUs. */
if (maxidx < 2)
return -1;
/* OK, we can use the CPUID instruction to get all info about the
caches. */
unsigned int cnt = 0;
unsigned int max = 1;
long int result = 0;
bool no_level_2_or_3 = false;
bool has_level_2 = false;
while (cnt++ < max)
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
__cpuid (2, eax, ebx, ecx, edx);
/* The low byte of EAX in the first round contain the number of
rounds we have to make. At least one, the one we are already
doing. */
if (cnt == 1)
{
max = eax & 0xff;
eax &= 0xffffff00;
}
/* Process the individual registers' value. */
result = intel_check_word (name, eax, &has_level_2,
&no_level_2_or_3, cpu_features);
if (result != 0)
return result;
result = intel_check_word (name, ebx, &has_level_2,
&no_level_2_or_3, cpu_features);
if (result != 0)
return result;
result = intel_check_word (name, ecx, &has_level_2,
&no_level_2_or_3, cpu_features);
if (result != 0)
return result;
result = intel_check_word (name, edx, &has_level_2,
&no_level_2_or_3, cpu_features);
if (result != 0)
return result;
}
if (name >= _SC_LEVEL2_CACHE_SIZE && name <= _SC_LEVEL3_CACHE_LINESIZE
&& no_level_2_or_3)
return -1;
return 0;
}
static long int __attribute__ ((noinline))
handle_amd (int name)
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
__cpuid (0x80000000, eax, ebx, ecx, edx);
/* No level 4 cache (yet). */
if (name > _SC_LEVEL3_CACHE_LINESIZE)
return 0;
unsigned int fn = 0x80000005 + (name >= _SC_LEVEL2_CACHE_SIZE);
if (eax < fn)
return 0;
__cpuid (fn, eax, ebx, ecx, edx);
if (name < _SC_LEVEL1_DCACHE_SIZE)
{
name += _SC_LEVEL1_DCACHE_SIZE - _SC_LEVEL1_ICACHE_SIZE;
ecx = edx;
}
switch (name)
{
case _SC_LEVEL1_DCACHE_SIZE:
return (ecx >> 14) & 0x3fc00;
case _SC_LEVEL1_DCACHE_ASSOC:
ecx >>= 16;
if ((ecx & 0xff) == 0xff)
/* Fully associative. */
return (ecx << 2) & 0x3fc00;
return ecx & 0xff;
case _SC_LEVEL1_DCACHE_LINESIZE:
return ecx & 0xff;
case _SC_LEVEL2_CACHE_SIZE:
return (ecx & 0xf000) == 0 ? 0 : (ecx >> 6) & 0x3fffc00;
case _SC_LEVEL2_CACHE_ASSOC:
switch ((ecx >> 12) & 0xf)
{
case 0:
case 1:
case 2:
case 4:
return (ecx >> 12) & 0xf;
case 6:
return 8;
case 8:
return 16;
case 10:
return 32;
case 11:
return 48;
case 12:
return 64;
case 13:
return 96;
case 14:
return 128;
case 15:
return ((ecx >> 6) & 0x3fffc00) / (ecx & 0xff);
default:
return 0;
}
/* NOTREACHED */
case _SC_LEVEL2_CACHE_LINESIZE:
return (ecx & 0xf000) == 0 ? 0 : ecx & 0xff;
case _SC_LEVEL3_CACHE_SIZE:
return (edx & 0xf000) == 0 ? 0 : (edx & 0x3ffc0000) << 1;
case _SC_LEVEL3_CACHE_ASSOC:
switch ((edx >> 12) & 0xf)
{
case 0:
case 1:
case 2:
case 4:
return (edx >> 12) & 0xf;
case 6:
return 8;
case 8:
return 16;
case 10:
return 32;
case 11:
return 48;
case 12:
return 64;
case 13:
return 96;
case 14:
return 128;
case 15:
return ((edx & 0x3ffc0000) << 1) / (edx & 0xff);
default:
return 0;
}
/* NOTREACHED */
case _SC_LEVEL3_CACHE_LINESIZE:
return (edx & 0xf000) == 0 ? 0 : edx & 0xff;
default:
assert (! "cannot happen");
}
return -1;
}
static long int __attribute__ ((noinline))
handle_zhaoxin (int name)
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
int folded_rel_name = (M(name) / 3) * 3;
unsigned int round = 0;
while (1)
{
__cpuid_count (4, round, eax, ebx, ecx, edx);
enum { null = 0, data = 1, inst = 2, uni = 3 } type = eax & 0x1f;
if (type == null)
break;
unsigned int level = (eax >> 5) & 0x7;
if ((level == 1 && type == data
&& folded_rel_name == M(_SC_LEVEL1_DCACHE_SIZE))
|| (level == 1 && type == inst
&& folded_rel_name == M(_SC_LEVEL1_ICACHE_SIZE))
|| (level == 2 && folded_rel_name == M(_SC_LEVEL2_CACHE_SIZE))
|| (level == 3 && folded_rel_name == M(_SC_LEVEL3_CACHE_SIZE)))
{
unsigned int offset = M(name) - folded_rel_name;
if (offset == 0)
/* Cache size. */
return (((ebx >> 22) + 1)
* (((ebx >> 12) & 0x3ff) + 1)
* ((ebx & 0xfff) + 1)
* (ecx + 1));
if (offset == 1)
return (ebx >> 22) + 1;
assert (offset == 2);
return (ebx & 0xfff) + 1;
}
++round;
}
/* Nothing found. */
return 0;
}