2014-07-16 19:32:44 +04:00
/*
* Record and handle CPU attributes .
*
* Copyright ( C ) 2014 ARM Ltd .
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* 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 , see < http : //www.gnu.org/licenses/>.
*/
# include <asm/arch_timer.h>
2017-03-10 23:32:23 +03:00
# include <asm/cache.h>
2014-07-16 19:32:44 +04:00
# include <asm/cpu.h>
# include <asm/cputype.h>
2014-11-14 18:54:09 +03:00
# include <asm/cpufeature.h>
2017-10-31 18:51:10 +03:00
# include <asm/fpsimd.h>
2014-07-16 19:32:44 +04:00
2014-07-16 19:32:45 +04:00
# include <linux/bitops.h>
2014-08-08 15:51:39 +04:00
# include <linux/bug.h>
2016-05-31 17:55:03 +03:00
# include <linux/compat.h>
# include <linux/elf.h>
2014-07-16 19:32:44 +04:00
# include <linux/init.h>
2014-07-16 19:32:46 +04:00
# include <linux/kernel.h>
2015-10-19 16:24:43 +03:00
# include <linux/personality.h>
2014-08-08 15:51:39 +04:00
# include <linux/preempt.h>
2014-07-16 19:32:45 +04:00
# include <linux/printk.h>
2015-10-19 16:24:43 +03:00
# include <linux/seq_file.h>
# include <linux/sched.h>
2014-07-16 19:32:44 +04:00
# include <linux/smp.h>
2015-11-18 21:48:55 +03:00
# include <linux/delay.h>
2014-07-16 19:32:44 +04:00
/*
* In case the boot CPU is hotpluggable , we record its initial state and
* current state separately . Certain system registers may contain different
* values depending on configuration at or after reset .
*/
DEFINE_PER_CPU ( struct cpuinfo_arm64 , cpu_data ) ;
static struct cpuinfo_arm64 boot_cpu_data ;
2014-07-16 19:32:45 +04:00
static char * icache_policy_str [ ] = {
2017-03-10 23:32:22 +03:00
[ 0 . . . ICACHE_POLICY_PIPT ] = " RESERVED/UNKNOWN " ,
[ ICACHE_POLICY_VIPT ] = " VIPT " ,
[ ICACHE_POLICY_PIPT ] = " PIPT " ,
2017-03-10 23:32:24 +03:00
[ ICACHE_POLICY_VPIPT ] = " VPIPT " ,
2014-07-16 19:32:45 +04:00
} ;
unsigned long __icache_flags ;
2015-07-30 18:36:25 +03:00
static const char * const hwcap_str [ ] = {
2015-10-19 16:24:43 +03:00
" fp " ,
" asimd " ,
" evtstrm " ,
" aes " ,
" pmull " ,
" sha1 " ,
" sha2 " ,
" crc32 " ,
" atomics " ,
2016-01-26 18:52:46 +03:00
" fphp " ,
" asimdhp " ,
2017-01-09 20:28:31 +03:00
" cpuid " ,
2017-01-12 19:37:28 +03:00
" asimdrdm " ,
2017-03-14 21:13:25 +03:00
" jscvt " ,
2017-03-14 21:13:26 +03:00
" fcma " ,
2017-03-14 21:13:27 +03:00
" lrcpc " ,
2017-07-25 13:55:40 +03:00
" dcpop " ,
2017-10-11 16:01:02 +03:00
" sha3 " ,
" sm3 " ,
" sm4 " ,
" asimddp " ,
" sha512 " ,
2017-10-31 18:51:19 +03:00
" sve " ,
2017-12-13 13:13:56 +03:00
" asimdfhm " ,
2018-03-12 13:04:14 +03:00
" dit " ,
" uscat " ,
" ilrcpc " ,
" flagm " ,
2018-06-15 13:37:34 +03:00
" ssbs " ,
2015-10-19 16:24:43 +03:00
NULL
} ;
# ifdef CONFIG_COMPAT
2015-07-30 18:36:25 +03:00
static const char * const compat_hwcap_str [ ] = {
2015-10-19 16:24:43 +03:00
" swp " ,
" half " ,
" thumb " ,
" 26bit " ,
" fastmult " ,
" fpa " ,
" vfp " ,
" edsp " ,
" java " ,
" iwmmxt " ,
" crunch " ,
" thumbee " ,
" neon " ,
" vfpv3 " ,
" vfpv3d16 " ,
" tls " ,
" vfpv4 " ,
" idiva " ,
" idivt " ,
" vfpd32 " ,
" lpae " ,
2016-05-10 17:40:31 +03:00
" evtstrm " ,
NULL
2015-10-19 16:24:43 +03:00
} ;
2015-07-30 18:36:25 +03:00
static const char * const compat_hwcap2_str [ ] = {
2015-10-19 16:24:43 +03:00
" aes " ,
" pmull " ,
" sha1 " ,
" sha2 " ,
" crc32 " ,
NULL
} ;
# endif /* CONFIG_COMPAT */
static int c_show ( struct seq_file * m , void * v )
{
int i , j ;
2016-05-31 17:55:03 +03:00
bool compat = personality ( current - > personality ) = = PER_LINUX32 ;
2015-10-19 16:24:43 +03:00
for_each_online_cpu ( i ) {
struct cpuinfo_arm64 * cpuinfo = & per_cpu ( cpu_data , i ) ;
u32 midr = cpuinfo - > reg_midr ;
/*
* glibc reads / proc / cpuinfo to determine the number of
* online processors , looking for lines beginning with
* " processor " . Give glibc what it expects .
*/
seq_printf ( m , " processor \t : %d \n " , i ) ;
2016-05-31 17:55:03 +03:00
if ( compat )
seq_printf ( m , " model name \t : ARMv8 Processor rev %d (%s) \n " ,
MIDR_REVISION ( midr ) , COMPAT_ELF_PLATFORM ) ;
2015-10-19 16:24:43 +03:00
2015-11-18 21:48:55 +03:00
seq_printf ( m , " BogoMIPS \t : %lu.%02lu \n " ,
loops_per_jiffy / ( 500000UL / HZ ) ,
loops_per_jiffy / ( 5000UL / HZ ) % 100 ) ;
2015-10-19 16:24:43 +03:00
/*
* Dump out the common processor features in a single line .
* Userspace should read the hwcaps with getauxval ( AT_HWCAP )
* rather than attempting to parse this , but there ' s a body of
* software which does already ( at least for 32 - bit ) .
*/
seq_puts ( m , " Features \t : " ) ;
2016-05-31 17:55:03 +03:00
if ( compat ) {
2015-10-19 16:24:43 +03:00
# ifdef CONFIG_COMPAT
for ( j = 0 ; compat_hwcap_str [ j ] ; j + + )
if ( compat_elf_hwcap & ( 1 < < j ) )
seq_printf ( m , " %s " , compat_hwcap_str [ j ] ) ;
for ( j = 0 ; compat_hwcap2_str [ j ] ; j + + )
if ( compat_elf_hwcap2 & ( 1 < < j ) )
seq_printf ( m , " %s " , compat_hwcap2_str [ j ] ) ;
# endif /* CONFIG_COMPAT */
} else {
for ( j = 0 ; hwcap_str [ j ] ; j + + )
if ( elf_hwcap & ( 1 < < j ) )
seq_printf ( m , " %s " , hwcap_str [ j ] ) ;
}
seq_puts ( m , " \n " ) ;
seq_printf ( m , " CPU implementer \t : 0x%02x \n " ,
MIDR_IMPLEMENTOR ( midr ) ) ;
seq_printf ( m , " CPU architecture: 8 \n " ) ;
seq_printf ( m , " CPU variant \t : 0x%x \n " , MIDR_VARIANT ( midr ) ) ;
seq_printf ( m , " CPU part \t : 0x%03x \n " , MIDR_PARTNUM ( midr ) ) ;
seq_printf ( m , " CPU revision \t : %d \n \n " , MIDR_REVISION ( midr ) ) ;
}
return 0 ;
}
static void * c_start ( struct seq_file * m , loff_t * pos )
{
return * pos < 1 ? ( void * ) 1 : NULL ;
}
static void * c_next ( struct seq_file * m , void * v , loff_t * pos )
{
+ + * pos ;
return NULL ;
}
static void c_stop ( struct seq_file * m , void * v )
{
}
const struct seq_operations cpuinfo_op = {
. start = c_start ,
. next = c_next ,
. stop = c_stop ,
. show = c_show
} ;
2016-07-08 18:01:13 +03:00
static struct kobj_type cpuregs_kobj_type = {
. sysfs_ops = & kobj_sysfs_ops ,
} ;
/*
* The ARM ARM uses the phrase " 32-bit register " to describe a register
* whose upper 32 bits are RES0 ( per C5 .1 .1 , ARM DDI 04 87 A . i ) , however
* no statement is made as to whether the upper 32 bits will or will not
* be made use of in future , and between ARM DDI 04 87 A . c and ARM DDI
* 04 87 A . d CLIDR_EL1 was expanded from 32 - bit to 64 - bit .
*
* Thus , while both MIDR_EL1 and REVIDR_EL1 are described as 32 - bit
* registers , we expose them both as 64 bit values to cater for possible
* future expansion without an ABI break .
*/
# define kobj_to_cpuinfo(kobj) container_of(kobj, struct cpuinfo_arm64, kobj)
# define CPUREGS_ATTR_RO(_name, _field) \
static ssize_t _name # # _show ( struct kobject * kobj , \
struct kobj_attribute * attr , char * buf ) \
{ \
struct cpuinfo_arm64 * info = kobj_to_cpuinfo ( kobj ) ; \
\
if ( info - > reg_midr ) \
return sprintf ( buf , " 0x%016x \n " , info - > reg_ # # _field ) ; \
else \
return 0 ; \
} \
static struct kobj_attribute cpuregs_attr_ # # _name = __ATTR_RO ( _name )
CPUREGS_ATTR_RO ( midr_el1 , midr ) ;
CPUREGS_ATTR_RO ( revidr_el1 , revidr ) ;
static struct attribute * cpuregs_id_attrs [ ] = {
& cpuregs_attr_midr_el1 . attr ,
& cpuregs_attr_revidr_el1 . attr ,
NULL
} ;
2017-06-30 14:30:20 +03:00
static const struct attribute_group cpuregs_attr_group = {
2016-07-08 18:01:13 +03:00
. attrs = cpuregs_id_attrs ,
. name = " identification "
} ;
2016-11-27 02:13:44 +03:00
static int cpuid_cpu_online ( unsigned int cpu )
2016-07-08 18:01:13 +03:00
{
int rc ;
struct device * dev ;
struct cpuinfo_arm64 * info = & per_cpu ( cpu_data , cpu ) ;
dev = get_cpu_device ( cpu ) ;
if ( ! dev ) {
rc = - ENODEV ;
goto out ;
}
rc = kobject_add ( & info - > kobj , & dev - > kobj , " regs " ) ;
if ( rc )
goto out ;
rc = sysfs_create_group ( & info - > kobj , & cpuregs_attr_group ) ;
if ( rc )
kobject_del ( & info - > kobj ) ;
out :
return rc ;
}
2016-11-27 02:13:44 +03:00
static int cpuid_cpu_offline ( unsigned int cpu )
2016-07-08 18:01:13 +03:00
{
struct device * dev ;
struct cpuinfo_arm64 * info = & per_cpu ( cpu_data , cpu ) ;
dev = get_cpu_device ( cpu ) ;
if ( ! dev )
return - ENODEV ;
if ( info - > kobj . parent ) {
sysfs_remove_group ( & info - > kobj , & cpuregs_attr_group ) ;
kobject_del ( & info - > kobj ) ;
}
return 0 ;
}
static int __init cpuinfo_regs_init ( void )
{
2016-11-27 02:13:44 +03:00
int cpu , ret ;
2016-07-08 18:01:13 +03:00
for_each_possible_cpu ( cpu ) {
struct cpuinfo_arm64 * info = & per_cpu ( cpu_data , cpu ) ;
kobject_init ( & info - > kobj , & cpuregs_kobj_type ) ;
}
2016-11-27 02:13:44 +03:00
ret = cpuhp_setup_state ( CPUHP_AP_ONLINE_DYN , " arm64/cpuinfo:online " ,
cpuid_cpu_online , cpuid_cpu_offline ) ;
if ( ret < 0 ) {
pr_err ( " cpuinfo: failed to register hotplug callbacks. \n " ) ;
return ret ;
}
2016-07-08 18:01:13 +03:00
return 0 ;
}
2014-07-16 19:32:45 +04:00
static void cpuinfo_detect_icache_policy ( struct cpuinfo_arm64 * info )
{
unsigned int cpu = smp_processor_id ( ) ;
u32 l1ip = CTR_L1IP ( info - > reg_ctr ) ;
2017-03-10 23:32:20 +03:00
switch ( l1ip ) {
case ICACHE_POLICY_PIPT :
break ;
2017-03-10 23:32:24 +03:00
case ICACHE_POLICY_VPIPT :
set_bit ( ICACHEF_VPIPT , & __icache_flags ) ;
break ;
2017-03-10 23:32:20 +03:00
default :
/* Fallthrough */
case ICACHE_POLICY_VIPT :
/* Assume aliasing */
set_bit ( ICACHEF_ALIASING , & __icache_flags ) ;
}
2014-07-16 19:32:45 +04:00
2014-08-01 13:23:20 +04:00
pr_info ( " Detected %s I-cache on CPU%d \n " , icache_policy_str [ l1ip ] , cpu ) ;
2014-07-16 19:32:45 +04:00
}
2014-07-16 19:32:44 +04:00
static void __cpuinfo_store_cpu ( struct cpuinfo_arm64 * info )
{
info - > reg_cntfrq = arch_timer_get_cntfrq ( ) ;
2018-10-09 16:47:06 +03:00
/*
* Use the effective value of the CTR_EL0 than the raw value
* exposed by the CPU . CTR_E0 . IDC field value must be interpreted
* with the CLIDR_EL1 fields to avoid triggering false warnings
* when there is a mismatch across the CPUs . Keep track of the
* effective value of the CTR_EL0 in our internal records for
* acurate sanity check and feature enablement .
*/
info - > reg_ctr = read_cpuid_effective_cachetype ( ) ;
2016-03-04 15:54:05 +03:00
info - > reg_dczid = read_cpuid ( DCZID_EL0 ) ;
2014-07-16 19:32:44 +04:00
info - > reg_midr = read_cpuid_id ( ) ;
2016-07-08 18:01:13 +03:00
info - > reg_revidr = read_cpuid ( REVIDR_EL1 ) ;
2014-07-16 19:32:44 +04:00
2016-03-04 15:54:05 +03:00
info - > reg_id_aa64dfr0 = read_cpuid ( ID_AA64DFR0_EL1 ) ;
info - > reg_id_aa64dfr1 = read_cpuid ( ID_AA64DFR1_EL1 ) ;
info - > reg_id_aa64isar0 = read_cpuid ( ID_AA64ISAR0_EL1 ) ;
info - > reg_id_aa64isar1 = read_cpuid ( ID_AA64ISAR1_EL1 ) ;
info - > reg_id_aa64mmfr0 = read_cpuid ( ID_AA64MMFR0_EL1 ) ;
info - > reg_id_aa64mmfr1 = read_cpuid ( ID_AA64MMFR1_EL1 ) ;
info - > reg_id_aa64mmfr2 = read_cpuid ( ID_AA64MMFR2_EL1 ) ;
info - > reg_id_aa64pfr0 = read_cpuid ( ID_AA64PFR0_EL1 ) ;
info - > reg_id_aa64pfr1 = read_cpuid ( ID_AA64PFR1_EL1 ) ;
2017-10-31 18:51:10 +03:00
info - > reg_id_aa64zfr0 = read_cpuid ( ID_AA64ZFR0_EL1 ) ;
2016-03-04 15:54:05 +03:00
2016-04-18 12:28:35 +03:00
/* Update the 32bit ID registers only if AArch32 is implemented */
if ( id_aa64pfr0_32bit_el0 ( info - > reg_id_aa64pfr0 ) ) {
info - > reg_id_dfr0 = read_cpuid ( ID_DFR0_EL1 ) ;
info - > reg_id_isar0 = read_cpuid ( ID_ISAR0_EL1 ) ;
info - > reg_id_isar1 = read_cpuid ( ID_ISAR1_EL1 ) ;
info - > reg_id_isar2 = read_cpuid ( ID_ISAR2_EL1 ) ;
info - > reg_id_isar3 = read_cpuid ( ID_ISAR3_EL1 ) ;
info - > reg_id_isar4 = read_cpuid ( ID_ISAR4_EL1 ) ;
info - > reg_id_isar5 = read_cpuid ( ID_ISAR5_EL1 ) ;
info - > reg_id_mmfr0 = read_cpuid ( ID_MMFR0_EL1 ) ;
info - > reg_id_mmfr1 = read_cpuid ( ID_MMFR1_EL1 ) ;
info - > reg_id_mmfr2 = read_cpuid ( ID_MMFR2_EL1 ) ;
info - > reg_id_mmfr3 = read_cpuid ( ID_MMFR3_EL1 ) ;
info - > reg_id_pfr0 = read_cpuid ( ID_PFR0_EL1 ) ;
info - > reg_id_pfr1 = read_cpuid ( ID_PFR1_EL1 ) ;
info - > reg_mvfr0 = read_cpuid ( MVFR0_EL1 ) ;
info - > reg_mvfr1 = read_cpuid ( MVFR1_EL1 ) ;
info - > reg_mvfr2 = read_cpuid ( MVFR2_EL1 ) ;
}
2015-01-07 13:31:56 +03:00
2017-10-31 18:51:10 +03:00
if ( IS_ENABLED ( CONFIG_ARM64_SVE ) & &
id_aa64pfr0_sve ( info - > reg_id_aa64pfr0 ) )
info - > reg_zcr = read_zcr_features ( ) ;
2014-07-16 19:32:45 +04:00
cpuinfo_detect_icache_policy ( info ) ;
2014-07-16 19:32:44 +04:00
}
void cpuinfo_store_cpu ( void )
{
struct cpuinfo_arm64 * info = this_cpu_ptr ( & cpu_data ) ;
__cpuinfo_store_cpu ( info ) ;
2015-10-19 16:24:46 +03:00
update_cpu_features ( smp_processor_id ( ) , info , & boot_cpu_data ) ;
2014-07-16 19:32:44 +04:00
}
void __init cpuinfo_store_boot_cpu ( void )
{
struct cpuinfo_arm64 * info = & per_cpu ( cpu_data , 0 ) ;
__cpuinfo_store_cpu ( info ) ;
boot_cpu_data = * info ;
2015-10-19 16:24:45 +03:00
init_cpu_features ( & boot_cpu_data ) ;
2014-07-16 19:32:44 +04:00
}
2016-07-08 18:01:13 +03:00
device_initcall ( cpuinfo_regs_init ) ;