mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
15
arch/arm/vfp/Makefile
Normal file
15
arch/arm/vfp/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# linux/arch/arm/vfp/Makefile
|
||||
#
|
||||
# Copyright (C) 2001 ARM Limited
|
||||
#
|
||||
|
||||
# ccflags-y := -DDEBUG
|
||||
# asflags-y := -DDEBUG
|
||||
|
||||
KBUILD_AFLAGS :=$(KBUILD_AFLAGS:-msoft-float=-Wa,-mfpu=softvfp+vfp -mfloat-abi=soft)
|
||||
LDFLAGS +=--no-warn-mismatch
|
||||
|
||||
obj-y += vfp.o
|
||||
|
||||
vfp-$(CONFIG_VFP) += vfpmodule.o entry.o vfphw.o vfpsingle.o vfpdouble.o
|
59
arch/arm/vfp/entry.S
Normal file
59
arch/arm/vfp/entry.S
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* linux/arch/arm/vfp/entry.S
|
||||
*
|
||||
* Copyright (C) 2004 ARM Limited.
|
||||
* Written by Deep Blue Solutions Limited.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/vfpmacros.h>
|
||||
#include <asm/assembler.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
|
||||
@ VFP entry point.
|
||||
@
|
||||
@ r0 = instruction opcode (32-bit ARM or two 16-bit Thumb)
|
||||
@ r2 = PC value to resume execution after successful emulation
|
||||
@ r9 = normal "successful" return address
|
||||
@ r10 = this threads thread_info structure
|
||||
@ lr = unrecognised instruction return address
|
||||
@ IRQs enabled.
|
||||
@
|
||||
ENTRY(do_vfp)
|
||||
inc_preempt_count r10, r4
|
||||
ldr r4, .LCvfp
|
||||
ldr r11, [r10, #TI_CPU] @ CPU number
|
||||
add r10, r10, #TI_VFPSTATE @ r10 = workspace
|
||||
ldr pc, [r4] @ call VFP entry point
|
||||
ENDPROC(do_vfp)
|
||||
|
||||
ENTRY(vfp_null_entry)
|
||||
dec_preempt_count_ti r10, r4
|
||||
ret lr
|
||||
ENDPROC(vfp_null_entry)
|
||||
|
||||
.align 2
|
||||
.LCvfp:
|
||||
.word vfp_vector
|
||||
|
||||
@ This code is called if the VFP does not exist. It needs to flag the
|
||||
@ failure to the VFP initialisation code.
|
||||
|
||||
__INIT
|
||||
ENTRY(vfp_testing_entry)
|
||||
dec_preempt_count_ti r10, r4
|
||||
ldr r0, VFP_arch_address
|
||||
str r0, [r0] @ set to non-zero value
|
||||
ret r9 @ we have handled the fault
|
||||
ENDPROC(vfp_testing_entry)
|
||||
|
||||
.align 2
|
||||
VFP_arch_address:
|
||||
.word VFP_arch
|
||||
|
||||
__FINIT
|
380
arch/arm/vfp/vfp.h
Normal file
380
arch/arm/vfp/vfp.h
Normal file
|
@ -0,0 +1,380 @@
|
|||
/*
|
||||
* linux/arch/arm/vfp/vfp.h
|
||||
*
|
||||
* Copyright (C) 2004 ARM Limited.
|
||||
* Written by Deep Blue Solutions Limited.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
static inline u32 vfp_shiftright32jamming(u32 val, unsigned int shift)
|
||||
{
|
||||
if (shift) {
|
||||
if (shift < 32)
|
||||
val = val >> shift | ((val << (32 - shift)) != 0);
|
||||
else
|
||||
val = val != 0;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u64 vfp_shiftright64jamming(u64 val, unsigned int shift)
|
||||
{
|
||||
if (shift) {
|
||||
if (shift < 64)
|
||||
val = val >> shift | ((val << (64 - shift)) != 0);
|
||||
else
|
||||
val = val != 0;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u32 vfp_hi64to32jamming(u64 val)
|
||||
{
|
||||
u32 v;
|
||||
|
||||
asm(
|
||||
"cmp %Q1, #1 @ vfp_hi64to32jamming\n\t"
|
||||
"movcc %0, %R1\n\t"
|
||||
"orrcs %0, %R1, #1"
|
||||
: "=r" (v) : "r" (val) : "cc");
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void add128(u64 *resh, u64 *resl, u64 nh, u64 nl, u64 mh, u64 ml)
|
||||
{
|
||||
asm( "adds %Q0, %Q2, %Q4\n\t"
|
||||
"adcs %R0, %R2, %R4\n\t"
|
||||
"adcs %Q1, %Q3, %Q5\n\t"
|
||||
"adc %R1, %R3, %R5"
|
||||
: "=r" (nl), "=r" (nh)
|
||||
: "0" (nl), "1" (nh), "r" (ml), "r" (mh)
|
||||
: "cc");
|
||||
*resh = nh;
|
||||
*resl = nl;
|
||||
}
|
||||
|
||||
static inline void sub128(u64 *resh, u64 *resl, u64 nh, u64 nl, u64 mh, u64 ml)
|
||||
{
|
||||
asm( "subs %Q0, %Q2, %Q4\n\t"
|
||||
"sbcs %R0, %R2, %R4\n\t"
|
||||
"sbcs %Q1, %Q3, %Q5\n\t"
|
||||
"sbc %R1, %R3, %R5\n\t"
|
||||
: "=r" (nl), "=r" (nh)
|
||||
: "0" (nl), "1" (nh), "r" (ml), "r" (mh)
|
||||
: "cc");
|
||||
*resh = nh;
|
||||
*resl = nl;
|
||||
}
|
||||
|
||||
static inline void mul64to128(u64 *resh, u64 *resl, u64 n, u64 m)
|
||||
{
|
||||
u32 nh, nl, mh, ml;
|
||||
u64 rh, rma, rmb, rl;
|
||||
|
||||
nl = n;
|
||||
ml = m;
|
||||
rl = (u64)nl * ml;
|
||||
|
||||
nh = n >> 32;
|
||||
rma = (u64)nh * ml;
|
||||
|
||||
mh = m >> 32;
|
||||
rmb = (u64)nl * mh;
|
||||
rma += rmb;
|
||||
|
||||
rh = (u64)nh * mh;
|
||||
rh += ((u64)(rma < rmb) << 32) + (rma >> 32);
|
||||
|
||||
rma <<= 32;
|
||||
rl += rma;
|
||||
rh += (rl < rma);
|
||||
|
||||
*resl = rl;
|
||||
*resh = rh;
|
||||
}
|
||||
|
||||
static inline void shift64left(u64 *resh, u64 *resl, u64 n)
|
||||
{
|
||||
*resh = n >> 63;
|
||||
*resl = n << 1;
|
||||
}
|
||||
|
||||
static inline u64 vfp_hi64multiply64(u64 n, u64 m)
|
||||
{
|
||||
u64 rh, rl;
|
||||
mul64to128(&rh, &rl, n, m);
|
||||
return rh | (rl != 0);
|
||||
}
|
||||
|
||||
static inline u64 vfp_estimate_div128to64(u64 nh, u64 nl, u64 m)
|
||||
{
|
||||
u64 mh, ml, remh, reml, termh, terml, z;
|
||||
|
||||
if (nh >= m)
|
||||
return ~0ULL;
|
||||
mh = m >> 32;
|
||||
if (mh << 32 <= nh) {
|
||||
z = 0xffffffff00000000ULL;
|
||||
} else {
|
||||
z = nh;
|
||||
do_div(z, mh);
|
||||
z <<= 32;
|
||||
}
|
||||
mul64to128(&termh, &terml, m, z);
|
||||
sub128(&remh, &reml, nh, nl, termh, terml);
|
||||
ml = m << 32;
|
||||
while ((s64)remh < 0) {
|
||||
z -= 0x100000000ULL;
|
||||
add128(&remh, &reml, remh, reml, mh, ml);
|
||||
}
|
||||
remh = (remh << 32) | (reml >> 32);
|
||||
if (mh << 32 <= remh) {
|
||||
z |= 0xffffffff;
|
||||
} else {
|
||||
do_div(remh, mh);
|
||||
z |= remh;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
/*
|
||||
* Operations on unpacked elements
|
||||
*/
|
||||
#define vfp_sign_negate(sign) (sign ^ 0x8000)
|
||||
|
||||
/*
|
||||
* Single-precision
|
||||
*/
|
||||
struct vfp_single {
|
||||
s16 exponent;
|
||||
u16 sign;
|
||||
u32 significand;
|
||||
};
|
||||
|
||||
extern s32 vfp_get_float(unsigned int reg);
|
||||
extern void vfp_put_float(s32 val, unsigned int reg);
|
||||
|
||||
/*
|
||||
* VFP_SINGLE_MANTISSA_BITS - number of bits in the mantissa
|
||||
* VFP_SINGLE_EXPONENT_BITS - number of bits in the exponent
|
||||
* VFP_SINGLE_LOW_BITS - number of low bits in the unpacked significand
|
||||
* which are not propagated to the float upon packing.
|
||||
*/
|
||||
#define VFP_SINGLE_MANTISSA_BITS (23)
|
||||
#define VFP_SINGLE_EXPONENT_BITS (8)
|
||||
#define VFP_SINGLE_LOW_BITS (32 - VFP_SINGLE_MANTISSA_BITS - 2)
|
||||
#define VFP_SINGLE_LOW_BITS_MASK ((1 << VFP_SINGLE_LOW_BITS) - 1)
|
||||
|
||||
/*
|
||||
* The bit in an unpacked float which indicates that it is a quiet NaN
|
||||
*/
|
||||
#define VFP_SINGLE_SIGNIFICAND_QNAN (1 << (VFP_SINGLE_MANTISSA_BITS - 1 + VFP_SINGLE_LOW_BITS))
|
||||
|
||||
/*
|
||||
* Operations on packed single-precision numbers
|
||||
*/
|
||||
#define vfp_single_packed_sign(v) ((v) & 0x80000000)
|
||||
#define vfp_single_packed_negate(v) ((v) ^ 0x80000000)
|
||||
#define vfp_single_packed_abs(v) ((v) & ~0x80000000)
|
||||
#define vfp_single_packed_exponent(v) (((v) >> VFP_SINGLE_MANTISSA_BITS) & ((1 << VFP_SINGLE_EXPONENT_BITS) - 1))
|
||||
#define vfp_single_packed_mantissa(v) ((v) & ((1 << VFP_SINGLE_MANTISSA_BITS) - 1))
|
||||
|
||||
/*
|
||||
* Unpack a single-precision float. Note that this returns the magnitude
|
||||
* of the single-precision float mantissa with the 1. if necessary,
|
||||
* aligned to bit 30.
|
||||
*/
|
||||
static inline void vfp_single_unpack(struct vfp_single *s, s32 val)
|
||||
{
|
||||
u32 significand;
|
||||
|
||||
s->sign = vfp_single_packed_sign(val) >> 16,
|
||||
s->exponent = vfp_single_packed_exponent(val);
|
||||
|
||||
significand = (u32) val;
|
||||
significand = (significand << (32 - VFP_SINGLE_MANTISSA_BITS)) >> 2;
|
||||
if (s->exponent && s->exponent != 255)
|
||||
significand |= 0x40000000;
|
||||
s->significand = significand;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-pack a single-precision float. This assumes that the float is
|
||||
* already normalised such that the MSB is bit 30, _not_ bit 31.
|
||||
*/
|
||||
static inline s32 vfp_single_pack(struct vfp_single *s)
|
||||
{
|
||||
u32 val;
|
||||
val = (s->sign << 16) +
|
||||
(s->exponent << VFP_SINGLE_MANTISSA_BITS) +
|
||||
(s->significand >> VFP_SINGLE_LOW_BITS);
|
||||
return (s32)val;
|
||||
}
|
||||
|
||||
#define VFP_NUMBER (1<<0)
|
||||
#define VFP_ZERO (1<<1)
|
||||
#define VFP_DENORMAL (1<<2)
|
||||
#define VFP_INFINITY (1<<3)
|
||||
#define VFP_NAN (1<<4)
|
||||
#define VFP_NAN_SIGNAL (1<<5)
|
||||
|
||||
#define VFP_QNAN (VFP_NAN)
|
||||
#define VFP_SNAN (VFP_NAN|VFP_NAN_SIGNAL)
|
||||
|
||||
static inline int vfp_single_type(struct vfp_single *s)
|
||||
{
|
||||
int type = VFP_NUMBER;
|
||||
if (s->exponent == 255) {
|
||||
if (s->significand == 0)
|
||||
type = VFP_INFINITY;
|
||||
else if (s->significand & VFP_SINGLE_SIGNIFICAND_QNAN)
|
||||
type = VFP_QNAN;
|
||||
else
|
||||
type = VFP_SNAN;
|
||||
} else if (s->exponent == 0) {
|
||||
if (s->significand == 0)
|
||||
type |= VFP_ZERO;
|
||||
else
|
||||
type |= VFP_DENORMAL;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
#ifndef DEBUG
|
||||
#define vfp_single_normaliseround(sd,vsd,fpscr,except,func) __vfp_single_normaliseround(sd,vsd,fpscr,except)
|
||||
u32 __vfp_single_normaliseround(int sd, struct vfp_single *vs, u32 fpscr, u32 exceptions);
|
||||
#else
|
||||
u32 vfp_single_normaliseround(int sd, struct vfp_single *vs, u32 fpscr, u32 exceptions, const char *func);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Double-precision
|
||||
*/
|
||||
struct vfp_double {
|
||||
s16 exponent;
|
||||
u16 sign;
|
||||
u64 significand;
|
||||
};
|
||||
|
||||
/*
|
||||
* VFP_REG_ZERO is a special register number for vfp_get_double
|
||||
* which returns (double)0.0. This is useful for the compare with
|
||||
* zero instructions.
|
||||
*/
|
||||
#ifdef CONFIG_VFPv3
|
||||
#define VFP_REG_ZERO 32
|
||||
#else
|
||||
#define VFP_REG_ZERO 16
|
||||
#endif
|
||||
extern u64 vfp_get_double(unsigned int reg);
|
||||
extern void vfp_put_double(u64 val, unsigned int reg);
|
||||
|
||||
#define VFP_DOUBLE_MANTISSA_BITS (52)
|
||||
#define VFP_DOUBLE_EXPONENT_BITS (11)
|
||||
#define VFP_DOUBLE_LOW_BITS (64 - VFP_DOUBLE_MANTISSA_BITS - 2)
|
||||
#define VFP_DOUBLE_LOW_BITS_MASK ((1 << VFP_DOUBLE_LOW_BITS) - 1)
|
||||
|
||||
/*
|
||||
* The bit in an unpacked double which indicates that it is a quiet NaN
|
||||
*/
|
||||
#define VFP_DOUBLE_SIGNIFICAND_QNAN (1ULL << (VFP_DOUBLE_MANTISSA_BITS - 1 + VFP_DOUBLE_LOW_BITS))
|
||||
|
||||
/*
|
||||
* Operations on packed single-precision numbers
|
||||
*/
|
||||
#define vfp_double_packed_sign(v) ((v) & (1ULL << 63))
|
||||
#define vfp_double_packed_negate(v) ((v) ^ (1ULL << 63))
|
||||
#define vfp_double_packed_abs(v) ((v) & ~(1ULL << 63))
|
||||
#define vfp_double_packed_exponent(v) (((v) >> VFP_DOUBLE_MANTISSA_BITS) & ((1 << VFP_DOUBLE_EXPONENT_BITS) - 1))
|
||||
#define vfp_double_packed_mantissa(v) ((v) & ((1ULL << VFP_DOUBLE_MANTISSA_BITS) - 1))
|
||||
|
||||
/*
|
||||
* Unpack a double-precision float. Note that this returns the magnitude
|
||||
* of the double-precision float mantissa with the 1. if necessary,
|
||||
* aligned to bit 62.
|
||||
*/
|
||||
static inline void vfp_double_unpack(struct vfp_double *s, s64 val)
|
||||
{
|
||||
u64 significand;
|
||||
|
||||
s->sign = vfp_double_packed_sign(val) >> 48;
|
||||
s->exponent = vfp_double_packed_exponent(val);
|
||||
|
||||
significand = (u64) val;
|
||||
significand = (significand << (64 - VFP_DOUBLE_MANTISSA_BITS)) >> 2;
|
||||
if (s->exponent && s->exponent != 2047)
|
||||
significand |= (1ULL << 62);
|
||||
s->significand = significand;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-pack a double-precision float. This assumes that the float is
|
||||
* already normalised such that the MSB is bit 30, _not_ bit 31.
|
||||
*/
|
||||
static inline s64 vfp_double_pack(struct vfp_double *s)
|
||||
{
|
||||
u64 val;
|
||||
val = ((u64)s->sign << 48) +
|
||||
((u64)s->exponent << VFP_DOUBLE_MANTISSA_BITS) +
|
||||
(s->significand >> VFP_DOUBLE_LOW_BITS);
|
||||
return (s64)val;
|
||||
}
|
||||
|
||||
static inline int vfp_double_type(struct vfp_double *s)
|
||||
{
|
||||
int type = VFP_NUMBER;
|
||||
if (s->exponent == 2047) {
|
||||
if (s->significand == 0)
|
||||
type = VFP_INFINITY;
|
||||
else if (s->significand & VFP_DOUBLE_SIGNIFICAND_QNAN)
|
||||
type = VFP_QNAN;
|
||||
else
|
||||
type = VFP_SNAN;
|
||||
} else if (s->exponent == 0) {
|
||||
if (s->significand == 0)
|
||||
type |= VFP_ZERO;
|
||||
else
|
||||
type |= VFP_DENORMAL;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
u32 vfp_double_normaliseround(int dd, struct vfp_double *vd, u32 fpscr, u32 exceptions, const char *func);
|
||||
|
||||
u32 vfp_estimate_sqrt_significand(u32 exponent, u32 significand);
|
||||
|
||||
/*
|
||||
* A special flag to tell the normalisation code not to normalise.
|
||||
*/
|
||||
#define VFP_NAN_FLAG 0x100
|
||||
|
||||
/*
|
||||
* A bit pattern used to indicate the initial (unset) value of the
|
||||
* exception mask, in case nothing handles an instruction. This
|
||||
* doesn't include the NAN flag, which get masked out before
|
||||
* we check for an error.
|
||||
*/
|
||||
#define VFP_EXCEPTION_ERROR ((u32)-1 & ~VFP_NAN_FLAG)
|
||||
|
||||
/*
|
||||
* A flag to tell vfp instruction type.
|
||||
* OP_SCALAR - this operation always operates in scalar mode
|
||||
* OP_SD - the instruction exceptionally writes to a single precision result.
|
||||
* OP_DD - the instruction exceptionally writes to a double precision result.
|
||||
* OP_SM - the instruction exceptionally reads from a single precision operand.
|
||||
*/
|
||||
#define OP_SCALAR (1 << 0)
|
||||
#define OP_SD (1 << 1)
|
||||
#define OP_DD (1 << 1)
|
||||
#define OP_SM (1 << 2)
|
||||
|
||||
struct op {
|
||||
u32 (* const fn)(int dd, int dn, int dm, u32 fpscr);
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
extern void vfp_save_state(void *location, u32 fpexc);
|
1206
arch/arm/vfp/vfpdouble.c
Normal file
1206
arch/arm/vfp/vfpdouble.c
Normal file
File diff suppressed because it is too large
Load diff
317
arch/arm/vfp/vfphw.S
Normal file
317
arch/arm/vfp/vfphw.S
Normal file
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* linux/arch/arm/vfp/vfphw.S
|
||||
*
|
||||
* Copyright (C) 2004 ARM Limited.
|
||||
* Written by Deep Blue Solutions Limited.
|
||||
*
|
||||
* 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 code is called from the kernel's undefined instruction trap.
|
||||
* r9 holds the return address for successful handling.
|
||||
* lr holds the return address for unrecognised instructions.
|
||||
* r10 points at the start of the private FP workspace in the thread structure
|
||||
* sp points to a struct pt_regs (as defined in include/asm/proc/ptrace.h)
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/vfpmacros.h>
|
||||
#include <linux/kern_levels.h>
|
||||
#include <asm/assembler.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
|
||||
.macro DBGSTR, str
|
||||
#ifdef DEBUG
|
||||
stmfd sp!, {r0-r3, ip, lr}
|
||||
ldr r0, =1f
|
||||
bl printk
|
||||
ldmfd sp!, {r0-r3, ip, lr}
|
||||
|
||||
.pushsection .rodata, "a"
|
||||
1: .ascii KERN_DEBUG "VFP: \str\n"
|
||||
.byte 0
|
||||
.previous
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.macro DBGSTR1, str, arg
|
||||
#ifdef DEBUG
|
||||
stmfd sp!, {r0-r3, ip, lr}
|
||||
mov r1, \arg
|
||||
ldr r0, =1f
|
||||
bl printk
|
||||
ldmfd sp!, {r0-r3, ip, lr}
|
||||
|
||||
.pushsection .rodata, "a"
|
||||
1: .ascii KERN_DEBUG "VFP: \str\n"
|
||||
.byte 0
|
||||
.previous
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.macro DBGSTR3, str, arg1, arg2, arg3
|
||||
#ifdef DEBUG
|
||||
stmfd sp!, {r0-r3, ip, lr}
|
||||
mov r3, \arg3
|
||||
mov r2, \arg2
|
||||
mov r1, \arg1
|
||||
ldr r0, =1f
|
||||
bl printk
|
||||
ldmfd sp!, {r0-r3, ip, lr}
|
||||
|
||||
.pushsection .rodata, "a"
|
||||
1: .ascii KERN_DEBUG "VFP: \str\n"
|
||||
.byte 0
|
||||
.previous
|
||||
#endif
|
||||
.endm
|
||||
|
||||
|
||||
@ VFP hardware support entry point.
|
||||
@
|
||||
@ r0 = instruction opcode (32-bit ARM or two 16-bit Thumb)
|
||||
@ r2 = PC value to resume execution after successful emulation
|
||||
@ r9 = normal "successful" return address
|
||||
@ r10 = vfp_state union
|
||||
@ r11 = CPU number
|
||||
@ lr = unrecognised instruction return address
|
||||
@ IRQs enabled.
|
||||
ENTRY(vfp_support_entry)
|
||||
DBGSTR3 "instr %08x pc %08x state %p", r0, r2, r10
|
||||
|
||||
ldr r3, [sp, #S_PSR] @ Neither lazy restore nor FP exceptions
|
||||
and r3, r3, #MODE_MASK @ are supported in kernel mode
|
||||
teq r3, #USR_MODE
|
||||
bne vfp_kmode_exception @ Returns through lr
|
||||
|
||||
VFPFMRX r1, FPEXC @ Is the VFP enabled?
|
||||
DBGSTR1 "fpexc %08x", r1
|
||||
tst r1, #FPEXC_EN
|
||||
bne look_for_VFP_exceptions @ VFP is already enabled
|
||||
|
||||
DBGSTR1 "enable %x", r10
|
||||
ldr r3, vfp_current_hw_state_address
|
||||
orr r1, r1, #FPEXC_EN @ user FPEXC has the enable bit set
|
||||
ldr r4, [r3, r11, lsl #2] @ vfp_current_hw_state pointer
|
||||
bic r5, r1, #FPEXC_EX @ make sure exceptions are disabled
|
||||
cmp r4, r10 @ this thread owns the hw context?
|
||||
#ifndef CONFIG_SMP
|
||||
@ For UP, checking that this thread owns the hw context is
|
||||
@ sufficient to determine that the hardware state is valid.
|
||||
beq vfp_hw_state_valid
|
||||
|
||||
@ On UP, we lazily save the VFP context. As a different
|
||||
@ thread wants ownership of the VFP hardware, save the old
|
||||
@ state if there was a previous (valid) owner.
|
||||
|
||||
VFPFMXR FPEXC, r5 @ enable VFP, disable any pending
|
||||
@ exceptions, so we can get at the
|
||||
@ rest of it
|
||||
|
||||
DBGSTR1 "save old state %p", r4
|
||||
cmp r4, #0 @ if the vfp_current_hw_state is NULL
|
||||
beq vfp_reload_hw @ then the hw state needs reloading
|
||||
VFPFSTMIA r4, r5 @ save the working registers
|
||||
VFPFMRX r5, FPSCR @ current status
|
||||
#ifndef CONFIG_CPU_FEROCEON
|
||||
tst r1, #FPEXC_EX @ is there additional state to save?
|
||||
beq 1f
|
||||
VFPFMRX r6, FPINST @ FPINST (only if FPEXC.EX is set)
|
||||
tst r1, #FPEXC_FP2V @ is there an FPINST2 to read?
|
||||
beq 1f
|
||||
VFPFMRX r8, FPINST2 @ FPINST2 if needed (and present)
|
||||
1:
|
||||
#endif
|
||||
stmia r4, {r1, r5, r6, r8} @ save FPEXC, FPSCR, FPINST, FPINST2
|
||||
vfp_reload_hw:
|
||||
|
||||
#else
|
||||
@ For SMP, if this thread does not own the hw context, then we
|
||||
@ need to reload it. No need to save the old state as on SMP,
|
||||
@ we always save the state when we switch away from a thread.
|
||||
bne vfp_reload_hw
|
||||
|
||||
@ This thread has ownership of the current hardware context.
|
||||
@ However, it may have been migrated to another CPU, in which
|
||||
@ case the saved state is newer than the hardware context.
|
||||
@ Check this by looking at the CPU number which the state was
|
||||
@ last loaded onto.
|
||||
ldr ip, [r10, #VFP_CPU]
|
||||
teq ip, r11
|
||||
beq vfp_hw_state_valid
|
||||
|
||||
vfp_reload_hw:
|
||||
@ We're loading this threads state into the VFP hardware. Update
|
||||
@ the CPU number which contains the most up to date VFP context.
|
||||
str r11, [r10, #VFP_CPU]
|
||||
|
||||
VFPFMXR FPEXC, r5 @ enable VFP, disable any pending
|
||||
@ exceptions, so we can get at the
|
||||
@ rest of it
|
||||
#endif
|
||||
|
||||
DBGSTR1 "load state %p", r10
|
||||
str r10, [r3, r11, lsl #2] @ update the vfp_current_hw_state pointer
|
||||
@ Load the saved state back into the VFP
|
||||
VFPFLDMIA r10, r5 @ reload the working registers while
|
||||
@ FPEXC is in a safe state
|
||||
ldmia r10, {r1, r5, r6, r8} @ load FPEXC, FPSCR, FPINST, FPINST2
|
||||
#ifndef CONFIG_CPU_FEROCEON
|
||||
tst r1, #FPEXC_EX @ is there additional state to restore?
|
||||
beq 1f
|
||||
VFPFMXR FPINST, r6 @ restore FPINST (only if FPEXC.EX is set)
|
||||
tst r1, #FPEXC_FP2V @ is there an FPINST2 to write?
|
||||
beq 1f
|
||||
VFPFMXR FPINST2, r8 @ FPINST2 if needed (and present)
|
||||
1:
|
||||
#endif
|
||||
VFPFMXR FPSCR, r5 @ restore status
|
||||
|
||||
@ The context stored in the VFP hardware is up to date with this thread
|
||||
vfp_hw_state_valid:
|
||||
tst r1, #FPEXC_EX
|
||||
bne process_exception @ might as well handle the pending
|
||||
@ exception before retrying branch
|
||||
@ out before setting an FPEXC that
|
||||
@ stops us reading stuff
|
||||
VFPFMXR FPEXC, r1 @ Restore FPEXC last
|
||||
sub r2, r2, #4 @ Retry current instruction - if Thumb
|
||||
str r2, [sp, #S_PC] @ mode it's two 16-bit instructions,
|
||||
@ else it's one 32-bit instruction, so
|
||||
@ always subtract 4 from the following
|
||||
@ instruction address.
|
||||
dec_preempt_count_ti r10, r4
|
||||
ret r9 @ we think we have handled things
|
||||
|
||||
|
||||
look_for_VFP_exceptions:
|
||||
@ Check for synchronous or asynchronous exception
|
||||
tst r1, #FPEXC_EX | FPEXC_DEX
|
||||
bne process_exception
|
||||
@ On some implementations of the VFP subarch 1, setting FPSCR.IXE
|
||||
@ causes all the CDP instructions to be bounced synchronously without
|
||||
@ setting the FPEXC.EX bit
|
||||
VFPFMRX r5, FPSCR
|
||||
tst r5, #FPSCR_IXE
|
||||
bne process_exception
|
||||
|
||||
@ Fall into hand on to next handler - appropriate coproc instr
|
||||
@ not recognised by VFP
|
||||
|
||||
DBGSTR "not VFP"
|
||||
dec_preempt_count_ti r10, r4
|
||||
ret lr
|
||||
|
||||
process_exception:
|
||||
DBGSTR "bounce"
|
||||
mov r2, sp @ nothing stacked - regdump is at TOS
|
||||
mov lr, r9 @ setup for a return to the user code.
|
||||
|
||||
@ Now call the C code to package up the bounce to the support code
|
||||
@ r0 holds the trigger instruction
|
||||
@ r1 holds the FPEXC value
|
||||
@ r2 pointer to register dump
|
||||
b VFP_bounce @ we have handled this - the support
|
||||
@ code will raise an exception if
|
||||
@ required. If not, the user code will
|
||||
@ retry the faulted instruction
|
||||
ENDPROC(vfp_support_entry)
|
||||
|
||||
ENTRY(vfp_save_state)
|
||||
@ Save the current VFP state
|
||||
@ r0 - save location
|
||||
@ r1 - FPEXC
|
||||
DBGSTR1 "save VFP state %p", r0
|
||||
VFPFSTMIA r0, r2 @ save the working registers
|
||||
VFPFMRX r2, FPSCR @ current status
|
||||
tst r1, #FPEXC_EX @ is there additional state to save?
|
||||
beq 1f
|
||||
VFPFMRX r3, FPINST @ FPINST (only if FPEXC.EX is set)
|
||||
tst r1, #FPEXC_FP2V @ is there an FPINST2 to read?
|
||||
beq 1f
|
||||
VFPFMRX r12, FPINST2 @ FPINST2 if needed (and present)
|
||||
1:
|
||||
stmia r0, {r1, r2, r3, r12} @ save FPEXC, FPSCR, FPINST, FPINST2
|
||||
ret lr
|
||||
ENDPROC(vfp_save_state)
|
||||
|
||||
.align
|
||||
vfp_current_hw_state_address:
|
||||
.word vfp_current_hw_state
|
||||
|
||||
.macro tbl_branch, base, tmp, shift
|
||||
#ifdef CONFIG_THUMB2_KERNEL
|
||||
adr \tmp, 1f
|
||||
add \tmp, \tmp, \base, lsl \shift
|
||||
ret \tmp
|
||||
#else
|
||||
add pc, pc, \base, lsl \shift
|
||||
mov r0, r0
|
||||
#endif
|
||||
1:
|
||||
.endm
|
||||
|
||||
ENTRY(vfp_get_float)
|
||||
tbl_branch r0, r3, #3
|
||||
.irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
1: mrc p10, 0, r0, c\dr, c0, 0 @ fmrs r0, s0
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
1: mrc p10, 0, r0, c\dr, c0, 4 @ fmrs r0, s1
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
.endr
|
||||
ENDPROC(vfp_get_float)
|
||||
|
||||
ENTRY(vfp_put_float)
|
||||
tbl_branch r1, r3, #3
|
||||
.irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
1: mcr p10, 0, r0, c\dr, c0, 0 @ fmsr r0, s0
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
1: mcr p10, 0, r0, c\dr, c0, 4 @ fmsr r0, s1
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
.endr
|
||||
ENDPROC(vfp_put_float)
|
||||
|
||||
ENTRY(vfp_get_double)
|
||||
tbl_branch r0, r3, #3
|
||||
.irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
1: fmrrd r0, r1, d\dr
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
.endr
|
||||
#ifdef CONFIG_VFPv3
|
||||
@ d16 - d31 registers
|
||||
.irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
1: mrrc p11, 3, r0, r1, c\dr @ fmrrd r0, r1, d\dr
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
.endr
|
||||
#endif
|
||||
|
||||
@ virtual register 16 (or 32 if VFPv3) for compare with zero
|
||||
mov r0, #0
|
||||
mov r1, #0
|
||||
ret lr
|
||||
ENDPROC(vfp_get_double)
|
||||
|
||||
ENTRY(vfp_put_double)
|
||||
tbl_branch r2, r3, #3
|
||||
.irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
1: fmdrr d\dr, r0, r1
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
.endr
|
||||
#ifdef CONFIG_VFPv3
|
||||
@ d16 - d31 registers
|
||||
.irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
1: mcrr p11, 3, r0, r1, c\dr @ fmdrr r0, r1, d\dr
|
||||
ret lr
|
||||
.org 1b + 8
|
||||
.endr
|
||||
#endif
|
||||
ENDPROC(vfp_put_double)
|
88
arch/arm/vfp/vfpinstr.h
Normal file
88
arch/arm/vfp/vfpinstr.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* linux/arch/arm/vfp/vfpinstr.h
|
||||
*
|
||||
* Copyright (C) 2004 ARM Limited.
|
||||
* Written by Deep Blue Solutions Limited.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* VFP instruction masks.
|
||||
*/
|
||||
#define INST_CPRTDO(inst) (((inst) & 0x0f000000) == 0x0e000000)
|
||||
#define INST_CPRT(inst) ((inst) & (1 << 4))
|
||||
#define INST_CPRT_L(inst) ((inst) & (1 << 20))
|
||||
#define INST_CPRT_Rd(inst) (((inst) & (15 << 12)) >> 12)
|
||||
#define INST_CPRT_OP(inst) (((inst) >> 21) & 7)
|
||||
#define INST_CPNUM(inst) ((inst) & 0xf00)
|
||||
#define CPNUM(cp) ((cp) << 8)
|
||||
|
||||
#define FOP_MASK (0x00b00040)
|
||||
#define FOP_FMAC (0x00000000)
|
||||
#define FOP_FNMAC (0x00000040)
|
||||
#define FOP_FMSC (0x00100000)
|
||||
#define FOP_FNMSC (0x00100040)
|
||||
#define FOP_FMUL (0x00200000)
|
||||
#define FOP_FNMUL (0x00200040)
|
||||
#define FOP_FADD (0x00300000)
|
||||
#define FOP_FSUB (0x00300040)
|
||||
#define FOP_FDIV (0x00800000)
|
||||
#define FOP_EXT (0x00b00040)
|
||||
|
||||
#define FOP_TO_IDX(inst) ((inst & 0x00b00000) >> 20 | (inst & (1 << 6)) >> 4)
|
||||
|
||||
#define FEXT_MASK (0x000f0080)
|
||||
#define FEXT_FCPY (0x00000000)
|
||||
#define FEXT_FABS (0x00000080)
|
||||
#define FEXT_FNEG (0x00010000)
|
||||
#define FEXT_FSQRT (0x00010080)
|
||||
#define FEXT_FCMP (0x00040000)
|
||||
#define FEXT_FCMPE (0x00040080)
|
||||
#define FEXT_FCMPZ (0x00050000)
|
||||
#define FEXT_FCMPEZ (0x00050080)
|
||||
#define FEXT_FCVT (0x00070080)
|
||||
#define FEXT_FUITO (0x00080000)
|
||||
#define FEXT_FSITO (0x00080080)
|
||||
#define FEXT_FTOUI (0x000c0000)
|
||||
#define FEXT_FTOUIZ (0x000c0080)
|
||||
#define FEXT_FTOSI (0x000d0000)
|
||||
#define FEXT_FTOSIZ (0x000d0080)
|
||||
|
||||
#define FEXT_TO_IDX(inst) ((inst & 0x000f0000) >> 15 | (inst & (1 << 7)) >> 7)
|
||||
|
||||
#define vfp_get_sd(inst) ((inst & 0x0000f000) >> 11 | (inst & (1 << 22)) >> 22)
|
||||
#define vfp_get_dd(inst) ((inst & 0x0000f000) >> 12 | (inst & (1 << 22)) >> 18)
|
||||
#define vfp_get_sm(inst) ((inst & 0x0000000f) << 1 | (inst & (1 << 5)) >> 5)
|
||||
#define vfp_get_dm(inst) ((inst & 0x0000000f) | (inst & (1 << 5)) >> 1)
|
||||
#define vfp_get_sn(inst) ((inst & 0x000f0000) >> 15 | (inst & (1 << 7)) >> 7)
|
||||
#define vfp_get_dn(inst) ((inst & 0x000f0000) >> 16 | (inst & (1 << 7)) >> 3)
|
||||
|
||||
#define vfp_single(inst) (((inst) & 0x0000f00) == 0xa00)
|
||||
|
||||
#define FPSCR_N (1 << 31)
|
||||
#define FPSCR_Z (1 << 30)
|
||||
#define FPSCR_C (1 << 29)
|
||||
#define FPSCR_V (1 << 28)
|
||||
|
||||
/*
|
||||
* Since we aren't building with -mfpu=vfp, we need to code
|
||||
* these instructions using their MRC/MCR equivalents.
|
||||
*/
|
||||
#define vfpreg(_vfp_) #_vfp_
|
||||
|
||||
#define fmrx(_vfp_) ({ \
|
||||
u32 __v; \
|
||||
asm("mrc p10, 7, %0, " vfpreg(_vfp_) ", cr0, 0 @ fmrx %0, " #_vfp_ \
|
||||
: "=r" (__v) : : "cc"); \
|
||||
__v; \
|
||||
})
|
||||
|
||||
#define fmxr(_vfp_,_var_) \
|
||||
asm("mcr p10, 7, %0, " vfpreg(_vfp_) ", cr0, 0 @ fmxr " #_vfp_ ", %0" \
|
||||
: : "r" (_var_) : "cc")
|
||||
|
||||
u32 vfp_single_cpdo(u32 inst, u32 fpscr);
|
||||
u32 vfp_single_cprt(u32 inst, u32 fpscr, struct pt_regs *regs);
|
||||
|
||||
u32 vfp_double_cpdo(u32 inst, u32 fpscr);
|
801
arch/arm/vfp/vfpmodule.c
Normal file
801
arch/arm/vfp/vfpmodule.c
Normal file
|
@ -0,0 +1,801 @@
|
|||
/*
|
||||
* linux/arch/arm/vfp/vfpmodule.c
|
||||
*
|
||||
* Copyright (C) 2004 ARM Limited.
|
||||
* Written by Deep Blue Solutions Limited.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/types.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpu_pm.h>
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/signal.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/user.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#include <asm/cp15.h>
|
||||
#include <asm/cputype.h>
|
||||
#include <asm/system_info.h>
|
||||
#include <asm/thread_notify.h>
|
||||
#include <asm/vfp.h>
|
||||
|
||||
#include "vfpinstr.h"
|
||||
#include "vfp.h"
|
||||
|
||||
/*
|
||||
* Our undef handlers (in entry.S)
|
||||
*/
|
||||
void vfp_testing_entry(void);
|
||||
void vfp_support_entry(void);
|
||||
void vfp_null_entry(void);
|
||||
|
||||
void (*vfp_vector)(void) = vfp_null_entry;
|
||||
|
||||
/*
|
||||
* Dual-use variable.
|
||||
* Used in startup: set to non-zero if VFP checks fail
|
||||
* After startup, holds VFP architecture
|
||||
*/
|
||||
unsigned int VFP_arch;
|
||||
|
||||
/*
|
||||
* The pointer to the vfpstate structure of the thread which currently
|
||||
* owns the context held in the VFP hardware, or NULL if the hardware
|
||||
* context is invalid.
|
||||
*
|
||||
* For UP, this is sufficient to tell which thread owns the VFP context.
|
||||
* However, for SMP, we also need to check the CPU number stored in the
|
||||
* saved state too to catch migrations.
|
||||
*/
|
||||
union vfp_state *vfp_current_hw_state[NR_CPUS];
|
||||
|
||||
/*
|
||||
* Is 'thread's most up to date state stored in this CPUs hardware?
|
||||
* Must be called from non-preemptible context.
|
||||
*/
|
||||
static bool vfp_state_in_hw(unsigned int cpu, struct thread_info *thread)
|
||||
{
|
||||
#ifdef CONFIG_SMP
|
||||
if (thread->vfpstate.hard.cpu != cpu)
|
||||
return false;
|
||||
#endif
|
||||
return vfp_current_hw_state[cpu] == &thread->vfpstate;
|
||||
}
|
||||
|
||||
/*
|
||||
* Force a reload of the VFP context from the thread structure. We do
|
||||
* this by ensuring that access to the VFP hardware is disabled, and
|
||||
* clear vfp_current_hw_state. Must be called from non-preemptible context.
|
||||
*/
|
||||
static void vfp_force_reload(unsigned int cpu, struct thread_info *thread)
|
||||
{
|
||||
if (vfp_state_in_hw(cpu, thread)) {
|
||||
fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
|
||||
vfp_current_hw_state[cpu] = NULL;
|
||||
}
|
||||
#ifdef CONFIG_SMP
|
||||
thread->vfpstate.hard.cpu = NR_CPUS;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Per-thread VFP initialization.
|
||||
*/
|
||||
static void vfp_thread_flush(struct thread_info *thread)
|
||||
{
|
||||
union vfp_state *vfp = &thread->vfpstate;
|
||||
unsigned int cpu;
|
||||
|
||||
/*
|
||||
* Disable VFP to ensure we initialize it first. We must ensure
|
||||
* that the modification of vfp_current_hw_state[] and hardware
|
||||
* disable are done for the same CPU and without preemption.
|
||||
*
|
||||
* Do this first to ensure that preemption won't overwrite our
|
||||
* state saving should access to the VFP be enabled at this point.
|
||||
*/
|
||||
cpu = get_cpu();
|
||||
if (vfp_current_hw_state[cpu] == vfp)
|
||||
vfp_current_hw_state[cpu] = NULL;
|
||||
fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
|
||||
put_cpu();
|
||||
|
||||
memset(vfp, 0, sizeof(union vfp_state));
|
||||
|
||||
vfp->hard.fpexc = FPEXC_EN;
|
||||
vfp->hard.fpscr = FPSCR_ROUND_NEAREST;
|
||||
#ifdef CONFIG_SMP
|
||||
vfp->hard.cpu = NR_CPUS;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void vfp_thread_exit(struct thread_info *thread)
|
||||
{
|
||||
/* release case: Per-thread VFP cleanup. */
|
||||
union vfp_state *vfp = &thread->vfpstate;
|
||||
unsigned int cpu = get_cpu();
|
||||
|
||||
if (vfp_current_hw_state[cpu] == vfp)
|
||||
vfp_current_hw_state[cpu] = NULL;
|
||||
put_cpu();
|
||||
}
|
||||
|
||||
static void vfp_thread_copy(struct thread_info *thread)
|
||||
{
|
||||
struct thread_info *parent = current_thread_info();
|
||||
|
||||
vfp_sync_hwstate(parent);
|
||||
thread->vfpstate = parent->vfpstate;
|
||||
#ifdef CONFIG_SMP
|
||||
thread->vfpstate.hard.cpu = NR_CPUS;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* When this function is called with the following 'cmd's, the following
|
||||
* is true while this function is being run:
|
||||
* THREAD_NOFTIFY_SWTICH:
|
||||
* - the previously running thread will not be scheduled onto another CPU.
|
||||
* - the next thread to be run (v) will not be running on another CPU.
|
||||
* - thread->cpu is the local CPU number
|
||||
* - not preemptible as we're called in the middle of a thread switch
|
||||
* THREAD_NOTIFY_FLUSH:
|
||||
* - the thread (v) will be running on the local CPU, so
|
||||
* v === current_thread_info()
|
||||
* - thread->cpu is the local CPU number at the time it is accessed,
|
||||
* but may change at any time.
|
||||
* - we could be preempted if tree preempt rcu is enabled, so
|
||||
* it is unsafe to use thread->cpu.
|
||||
* THREAD_NOTIFY_EXIT
|
||||
* - the thread (v) will be running on the local CPU, so
|
||||
* v === current_thread_info()
|
||||
* - thread->cpu is the local CPU number at the time it is accessed,
|
||||
* but may change at any time.
|
||||
* - we could be preempted if tree preempt rcu is enabled, so
|
||||
* it is unsafe to use thread->cpu.
|
||||
*/
|
||||
static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v)
|
||||
{
|
||||
struct thread_info *thread = v;
|
||||
u32 fpexc;
|
||||
#ifdef CONFIG_SMP
|
||||
unsigned int cpu;
|
||||
#endif
|
||||
|
||||
switch (cmd) {
|
||||
case THREAD_NOTIFY_SWITCH:
|
||||
fpexc = fmrx(FPEXC);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
cpu = thread->cpu;
|
||||
|
||||
/*
|
||||
* On SMP, if VFP is enabled, save the old state in
|
||||
* case the thread migrates to a different CPU. The
|
||||
* restoring is done lazily.
|
||||
*/
|
||||
if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu])
|
||||
vfp_save_state(vfp_current_hw_state[cpu], fpexc);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Always disable VFP so we can lazily save/restore the
|
||||
* old state.
|
||||
*/
|
||||
fmxr(FPEXC, fpexc & ~FPEXC_EN);
|
||||
break;
|
||||
|
||||
case THREAD_NOTIFY_FLUSH:
|
||||
vfp_thread_flush(thread);
|
||||
break;
|
||||
|
||||
case THREAD_NOTIFY_EXIT:
|
||||
vfp_thread_exit(thread);
|
||||
break;
|
||||
|
||||
case THREAD_NOTIFY_COPY:
|
||||
vfp_thread_copy(thread);
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block vfp_notifier_block = {
|
||||
.notifier_call = vfp_notifier,
|
||||
};
|
||||
|
||||
/*
|
||||
* Raise a SIGFPE for the current process.
|
||||
* sicode describes the signal being raised.
|
||||
*/
|
||||
static void vfp_raise_sigfpe(unsigned int sicode, struct pt_regs *regs)
|
||||
{
|
||||
siginfo_t info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
|
||||
info.si_signo = SIGFPE;
|
||||
info.si_code = sicode;
|
||||
info.si_addr = (void __user *)(instruction_pointer(regs) - 4);
|
||||
|
||||
/*
|
||||
* This is the same as NWFPE, because it's not clear what
|
||||
* this is used for
|
||||
*/
|
||||
current->thread.error_code = 0;
|
||||
current->thread.trap_no = 6;
|
||||
|
||||
send_sig_info(SIGFPE, &info, current);
|
||||
}
|
||||
|
||||
static void vfp_panic(char *reason, u32 inst)
|
||||
{
|
||||
int i;
|
||||
|
||||
pr_err("VFP: Error: %s\n", reason);
|
||||
pr_err("VFP: EXC 0x%08x SCR 0x%08x INST 0x%08x\n",
|
||||
fmrx(FPEXC), fmrx(FPSCR), inst);
|
||||
for (i = 0; i < 32; i += 2)
|
||||
pr_err("VFP: s%2u: 0x%08x s%2u: 0x%08x\n",
|
||||
i, vfp_get_float(i), i+1, vfp_get_float(i+1));
|
||||
}
|
||||
|
||||
/*
|
||||
* Process bitmask of exception conditions.
|
||||
*/
|
||||
static void vfp_raise_exceptions(u32 exceptions, u32 inst, u32 fpscr, struct pt_regs *regs)
|
||||
{
|
||||
int si_code = 0;
|
||||
|
||||
pr_debug("VFP: raising exceptions %08x\n", exceptions);
|
||||
|
||||
if (exceptions == VFP_EXCEPTION_ERROR) {
|
||||
vfp_panic("unhandled bounce", inst);
|
||||
vfp_raise_sigfpe(0, regs);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If any of the status flags are set, update the FPSCR.
|
||||
* Comparison instructions always return at least one of
|
||||
* these flags set.
|
||||
*/
|
||||
if (exceptions & (FPSCR_N|FPSCR_Z|FPSCR_C|FPSCR_V))
|
||||
fpscr &= ~(FPSCR_N|FPSCR_Z|FPSCR_C|FPSCR_V);
|
||||
|
||||
fpscr |= exceptions;
|
||||
|
||||
fmxr(FPSCR, fpscr);
|
||||
|
||||
#define RAISE(stat,en,sig) \
|
||||
if (exceptions & stat && fpscr & en) \
|
||||
si_code = sig;
|
||||
|
||||
/*
|
||||
* These are arranged in priority order, least to highest.
|
||||
*/
|
||||
RAISE(FPSCR_DZC, FPSCR_DZE, FPE_FLTDIV);
|
||||
RAISE(FPSCR_IXC, FPSCR_IXE, FPE_FLTRES);
|
||||
RAISE(FPSCR_UFC, FPSCR_UFE, FPE_FLTUND);
|
||||
RAISE(FPSCR_OFC, FPSCR_OFE, FPE_FLTOVF);
|
||||
RAISE(FPSCR_IOC, FPSCR_IOE, FPE_FLTINV);
|
||||
|
||||
if (si_code)
|
||||
vfp_raise_sigfpe(si_code, regs);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emulate a VFP instruction.
|
||||
*/
|
||||
static u32 vfp_emulate_instruction(u32 inst, u32 fpscr, struct pt_regs *regs)
|
||||
{
|
||||
u32 exceptions = VFP_EXCEPTION_ERROR;
|
||||
|
||||
pr_debug("VFP: emulate: INST=0x%08x SCR=0x%08x\n", inst, fpscr);
|
||||
|
||||
if (INST_CPRTDO(inst)) {
|
||||
if (!INST_CPRT(inst)) {
|
||||
/*
|
||||
* CPDO
|
||||
*/
|
||||
if (vfp_single(inst)) {
|
||||
exceptions = vfp_single_cpdo(inst, fpscr);
|
||||
} else {
|
||||
exceptions = vfp_double_cpdo(inst, fpscr);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* A CPRT instruction can not appear in FPINST2, nor
|
||||
* can it cause an exception. Therefore, we do not
|
||||
* have to emulate it.
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* A CPDT instruction can not appear in FPINST2, nor can
|
||||
* it cause an exception. Therefore, we do not have to
|
||||
* emulate it.
|
||||
*/
|
||||
}
|
||||
return exceptions & ~VFP_NAN_FLAG;
|
||||
}
|
||||
|
||||
/*
|
||||
* Package up a bounce condition.
|
||||
*/
|
||||
void VFP_bounce(u32 trigger, u32 fpexc, struct pt_regs *regs)
|
||||
{
|
||||
u32 fpscr, orig_fpscr, fpsid, exceptions;
|
||||
|
||||
pr_debug("VFP: bounce: trigger %08x fpexc %08x\n", trigger, fpexc);
|
||||
|
||||
/*
|
||||
* At this point, FPEXC can have the following configuration:
|
||||
*
|
||||
* EX DEX IXE
|
||||
* 0 1 x - synchronous exception
|
||||
* 1 x 0 - asynchronous exception
|
||||
* 1 x 1 - sychronous on VFP subarch 1 and asynchronous on later
|
||||
* 0 0 1 - synchronous on VFP9 (non-standard subarch 1
|
||||
* implementation), undefined otherwise
|
||||
*
|
||||
* Clear various bits and enable access to the VFP so we can
|
||||
* handle the bounce.
|
||||
*/
|
||||
fmxr(FPEXC, fpexc & ~(FPEXC_EX|FPEXC_DEX|FPEXC_FP2V|FPEXC_VV|FPEXC_TRAP_MASK));
|
||||
|
||||
fpsid = fmrx(FPSID);
|
||||
orig_fpscr = fpscr = fmrx(FPSCR);
|
||||
|
||||
/*
|
||||
* Check for the special VFP subarch 1 and FPSCR.IXE bit case
|
||||
*/
|
||||
if ((fpsid & FPSID_ARCH_MASK) == (1 << FPSID_ARCH_BIT)
|
||||
&& (fpscr & FPSCR_IXE)) {
|
||||
/*
|
||||
* Synchronous exception, emulate the trigger instruction
|
||||
*/
|
||||
goto emulate;
|
||||
}
|
||||
|
||||
if (fpexc & FPEXC_EX) {
|
||||
#ifndef CONFIG_CPU_FEROCEON
|
||||
/*
|
||||
* Asynchronous exception. The instruction is read from FPINST
|
||||
* and the interrupted instruction has to be restarted.
|
||||
*/
|
||||
trigger = fmrx(FPINST);
|
||||
regs->ARM_pc -= 4;
|
||||
#endif
|
||||
} else if (!(fpexc & FPEXC_DEX)) {
|
||||
/*
|
||||
* Illegal combination of bits. It can be caused by an
|
||||
* unallocated VFP instruction but with FPSCR.IXE set and not
|
||||
* on VFP subarch 1.
|
||||
*/
|
||||
vfp_raise_exceptions(VFP_EXCEPTION_ERROR, trigger, fpscr, regs);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Modify fpscr to indicate the number of iterations remaining.
|
||||
* If FPEXC.EX is 0, FPEXC.DEX is 1 and the FPEXC.VV bit indicates
|
||||
* whether FPEXC.VECITR or FPSCR.LEN is used.
|
||||
*/
|
||||
if (fpexc & (FPEXC_EX | FPEXC_VV)) {
|
||||
u32 len;
|
||||
|
||||
len = fpexc + (1 << FPEXC_LENGTH_BIT);
|
||||
|
||||
fpscr &= ~FPSCR_LENGTH_MASK;
|
||||
fpscr |= (len & FPEXC_LENGTH_MASK) << (FPSCR_LENGTH_BIT - FPEXC_LENGTH_BIT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the first FP instruction. We used to take note of the
|
||||
* FPEXC bounce reason, but this appears to be unreliable.
|
||||
* Emulate the bounced instruction instead.
|
||||
*/
|
||||
exceptions = vfp_emulate_instruction(trigger, fpscr, regs);
|
||||
if (exceptions)
|
||||
vfp_raise_exceptions(exceptions, trigger, orig_fpscr, regs);
|
||||
|
||||
/*
|
||||
* If there isn't a second FP instruction, exit now. Note that
|
||||
* the FPEXC.FP2V bit is valid only if FPEXC.EX is 1.
|
||||
*/
|
||||
if ((fpexc & (FPEXC_EX | FPEXC_FP2V)) != (FPEXC_EX | FPEXC_FP2V))
|
||||
goto exit;
|
||||
|
||||
/*
|
||||
* The barrier() here prevents fpinst2 being read
|
||||
* before the condition above.
|
||||
*/
|
||||
barrier();
|
||||
trigger = fmrx(FPINST2);
|
||||
|
||||
emulate:
|
||||
exceptions = vfp_emulate_instruction(trigger, orig_fpscr, regs);
|
||||
if (exceptions)
|
||||
vfp_raise_exceptions(exceptions, trigger, orig_fpscr, regs);
|
||||
exit:
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
static void vfp_enable(void *unused)
|
||||
{
|
||||
u32 access;
|
||||
|
||||
BUG_ON(preemptible());
|
||||
access = get_copro_access();
|
||||
|
||||
/*
|
||||
* Enable full access to VFP (cp10 and cp11)
|
||||
*/
|
||||
set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_PM
|
||||
static int vfp_pm_suspend(void)
|
||||
{
|
||||
struct thread_info *ti = current_thread_info();
|
||||
u32 fpexc = fmrx(FPEXC);
|
||||
|
||||
/* if vfp is on, then save state for resumption */
|
||||
if (fpexc & FPEXC_EN) {
|
||||
pr_debug("%s: saving vfp state\n", __func__);
|
||||
vfp_save_state(&ti->vfpstate, fpexc);
|
||||
|
||||
/* disable, just in case */
|
||||
fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
|
||||
} else if (vfp_current_hw_state[ti->cpu]) {
|
||||
#ifndef CONFIG_SMP
|
||||
fmxr(FPEXC, fpexc | FPEXC_EN);
|
||||
vfp_save_state(vfp_current_hw_state[ti->cpu], fpexc);
|
||||
fmxr(FPEXC, fpexc);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* clear any information we had about last context state */
|
||||
vfp_current_hw_state[ti->cpu] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vfp_pm_resume(void)
|
||||
{
|
||||
/* ensure we have access to the vfp */
|
||||
vfp_enable(NULL);
|
||||
|
||||
/* and disable it to ensure the next usage restores the state */
|
||||
fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
|
||||
}
|
||||
|
||||
static int vfp_cpu_pm_notifier(struct notifier_block *self, unsigned long cmd,
|
||||
void *v)
|
||||
{
|
||||
switch (cmd) {
|
||||
case CPU_PM_ENTER:
|
||||
vfp_pm_suspend();
|
||||
break;
|
||||
case CPU_PM_ENTER_FAILED:
|
||||
case CPU_PM_EXIT:
|
||||
vfp_pm_resume();
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block vfp_cpu_pm_notifier_block = {
|
||||
.notifier_call = vfp_cpu_pm_notifier,
|
||||
};
|
||||
|
||||
static void vfp_pm_init(void)
|
||||
{
|
||||
cpu_pm_register_notifier(&vfp_cpu_pm_notifier_block);
|
||||
}
|
||||
|
||||
#else
|
||||
static inline void vfp_pm_init(void) { }
|
||||
#endif /* CONFIG_CPU_PM */
|
||||
|
||||
/*
|
||||
* Ensure that the VFP state stored in 'thread->vfpstate' is up to date
|
||||
* with the hardware state.
|
||||
*/
|
||||
void vfp_sync_hwstate(struct thread_info *thread)
|
||||
{
|
||||
unsigned int cpu = get_cpu();
|
||||
|
||||
if (vfp_state_in_hw(cpu, thread)) {
|
||||
u32 fpexc = fmrx(FPEXC);
|
||||
|
||||
/*
|
||||
* Save the last VFP state on this CPU.
|
||||
*/
|
||||
fmxr(FPEXC, fpexc | FPEXC_EN);
|
||||
vfp_save_state(&thread->vfpstate, fpexc | FPEXC_EN);
|
||||
fmxr(FPEXC, fpexc);
|
||||
}
|
||||
|
||||
put_cpu();
|
||||
}
|
||||
|
||||
/* Ensure that the thread reloads the hardware VFP state on the next use. */
|
||||
void vfp_flush_hwstate(struct thread_info *thread)
|
||||
{
|
||||
unsigned int cpu = get_cpu();
|
||||
|
||||
vfp_force_reload(cpu, thread);
|
||||
|
||||
put_cpu();
|
||||
}
|
||||
|
||||
/*
|
||||
* Save the current VFP state into the provided structures and prepare
|
||||
* for entry into a new function (signal handler).
|
||||
*/
|
||||
int vfp_preserve_user_clear_hwstate(struct user_vfp __user *ufp,
|
||||
struct user_vfp_exc __user *ufp_exc)
|
||||
{
|
||||
struct thread_info *thread = current_thread_info();
|
||||
struct vfp_hard_struct *hwstate = &thread->vfpstate.hard;
|
||||
int err = 0;
|
||||
|
||||
/* Ensure that the saved hwstate is up-to-date. */
|
||||
vfp_sync_hwstate(thread);
|
||||
|
||||
/*
|
||||
* Copy the floating point registers. There can be unused
|
||||
* registers see asm/hwcap.h for details.
|
||||
*/
|
||||
err |= __copy_to_user(&ufp->fpregs, &hwstate->fpregs,
|
||||
sizeof(hwstate->fpregs));
|
||||
/*
|
||||
* Copy the status and control register.
|
||||
*/
|
||||
__put_user_error(hwstate->fpscr, &ufp->fpscr, err);
|
||||
|
||||
/*
|
||||
* Copy the exception registers.
|
||||
*/
|
||||
__put_user_error(hwstate->fpexc, &ufp_exc->fpexc, err);
|
||||
__put_user_error(hwstate->fpinst, &ufp_exc->fpinst, err);
|
||||
__put_user_error(hwstate->fpinst2, &ufp_exc->fpinst2, err);
|
||||
|
||||
if (err)
|
||||
return -EFAULT;
|
||||
|
||||
/* Ensure that VFP is disabled. */
|
||||
vfp_flush_hwstate(thread);
|
||||
|
||||
/*
|
||||
* As per the PCS, clear the length and stride bits for function
|
||||
* entry.
|
||||
*/
|
||||
hwstate->fpscr &= ~(FPSCR_LENGTH_MASK | FPSCR_STRIDE_MASK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sanitise and restore the current VFP state from the provided structures. */
|
||||
int vfp_restore_user_hwstate(struct user_vfp __user *ufp,
|
||||
struct user_vfp_exc __user *ufp_exc)
|
||||
{
|
||||
struct thread_info *thread = current_thread_info();
|
||||
struct vfp_hard_struct *hwstate = &thread->vfpstate.hard;
|
||||
unsigned long fpexc;
|
||||
int err = 0;
|
||||
|
||||
/* Disable VFP to avoid corrupting the new thread state. */
|
||||
vfp_flush_hwstate(thread);
|
||||
|
||||
/*
|
||||
* Copy the floating point registers. There can be unused
|
||||
* registers see asm/hwcap.h for details.
|
||||
*/
|
||||
err |= __copy_from_user(&hwstate->fpregs, &ufp->fpregs,
|
||||
sizeof(hwstate->fpregs));
|
||||
/*
|
||||
* Copy the status and control register.
|
||||
*/
|
||||
__get_user_error(hwstate->fpscr, &ufp->fpscr, err);
|
||||
|
||||
/*
|
||||
* Sanitise and restore the exception registers.
|
||||
*/
|
||||
__get_user_error(fpexc, &ufp_exc->fpexc, err);
|
||||
|
||||
/* Ensure the VFP is enabled. */
|
||||
fpexc |= FPEXC_EN;
|
||||
|
||||
/* Ensure FPINST2 is invalid and the exception flag is cleared. */
|
||||
fpexc &= ~(FPEXC_EX | FPEXC_FP2V);
|
||||
hwstate->fpexc = fpexc;
|
||||
|
||||
__get_user_error(hwstate->fpinst, &ufp_exc->fpinst, err);
|
||||
__get_user_error(hwstate->fpinst2, &ufp_exc->fpinst2, err);
|
||||
|
||||
return err ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* VFP hardware can lose all context when a CPU goes offline.
|
||||
* As we will be running in SMP mode with CPU hotplug, we will save the
|
||||
* hardware state at every thread switch. We clear our held state when
|
||||
* a CPU has been killed, indicating that the VFP hardware doesn't contain
|
||||
* a threads VFP state. When a CPU starts up, we re-enable access to the
|
||||
* VFP hardware.
|
||||
*
|
||||
* Both CPU_DYING and CPU_STARTING are called on the CPU which
|
||||
* is being offlined/onlined.
|
||||
*/
|
||||
static int vfp_hotplug(struct notifier_block *b, unsigned long action,
|
||||
void *hcpu)
|
||||
{
|
||||
if (action == CPU_DYING || action == CPU_DYING_FROZEN)
|
||||
vfp_current_hw_state[(long)hcpu] = NULL;
|
||||
else if (action == CPU_STARTING || action == CPU_STARTING_FROZEN)
|
||||
vfp_enable(NULL);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
void vfp_kmode_exception(void)
|
||||
{
|
||||
/*
|
||||
* If we reach this point, a floating point exception has been raised
|
||||
* while running in kernel mode. If the NEON/VFP unit was enabled at the
|
||||
* time, it means a VFP instruction has been issued that requires
|
||||
* software assistance to complete, something which is not currently
|
||||
* supported in kernel mode.
|
||||
* If the NEON/VFP unit was disabled, and the location pointed to below
|
||||
* is properly preceded by a call to kernel_neon_begin(), something has
|
||||
* caused the task to be scheduled out and back in again. In this case,
|
||||
* rebuilding and running with CONFIG_DEBUG_ATOMIC_SLEEP enabled should
|
||||
* be helpful in localizing the problem.
|
||||
*/
|
||||
if (fmrx(FPEXC) & FPEXC_EN)
|
||||
pr_crit("BUG: unsupported FP instruction in kernel mode\n");
|
||||
else
|
||||
pr_crit("BUG: FP instruction issued in kernel mode with FP unit disabled\n");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MODE_NEON
|
||||
|
||||
/*
|
||||
* Kernel-side NEON support functions
|
||||
*/
|
||||
void kernel_neon_begin(void)
|
||||
{
|
||||
struct thread_info *thread = current_thread_info();
|
||||
unsigned int cpu;
|
||||
u32 fpexc;
|
||||
|
||||
/*
|
||||
* Kernel mode NEON is only allowed outside of interrupt context
|
||||
* with preemption disabled. This will make sure that the kernel
|
||||
* mode NEON register contents never need to be preserved.
|
||||
*/
|
||||
BUG_ON(in_interrupt());
|
||||
cpu = get_cpu();
|
||||
|
||||
fpexc = fmrx(FPEXC) | FPEXC_EN;
|
||||
fmxr(FPEXC, fpexc);
|
||||
|
||||
/*
|
||||
* Save the userland NEON/VFP state. Under UP,
|
||||
* the owner could be a task other than 'current'
|
||||
*/
|
||||
if (vfp_state_in_hw(cpu, thread))
|
||||
vfp_save_state(&thread->vfpstate, fpexc);
|
||||
#ifndef CONFIG_SMP
|
||||
else if (vfp_current_hw_state[cpu] != NULL)
|
||||
vfp_save_state(vfp_current_hw_state[cpu], fpexc);
|
||||
#endif
|
||||
vfp_current_hw_state[cpu] = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(kernel_neon_begin);
|
||||
|
||||
void kernel_neon_end(void)
|
||||
{
|
||||
/* Disable the NEON/VFP unit. */
|
||||
fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
|
||||
put_cpu();
|
||||
}
|
||||
EXPORT_SYMBOL(kernel_neon_end);
|
||||
|
||||
#endif /* CONFIG_KERNEL_MODE_NEON */
|
||||
|
||||
/*
|
||||
* VFP support code initialisation.
|
||||
*/
|
||||
static int __init vfp_init(void)
|
||||
{
|
||||
unsigned int vfpsid;
|
||||
unsigned int cpu_arch = cpu_architecture();
|
||||
|
||||
if (cpu_arch >= CPU_ARCH_ARMv6)
|
||||
on_each_cpu(vfp_enable, NULL, 1);
|
||||
|
||||
/*
|
||||
* First check that there is a VFP that we can use.
|
||||
* The handler is already setup to just log calls, so
|
||||
* we just need to read the VFPSID register.
|
||||
*/
|
||||
vfp_vector = vfp_testing_entry;
|
||||
barrier();
|
||||
vfpsid = fmrx(FPSID);
|
||||
barrier();
|
||||
vfp_vector = vfp_null_entry;
|
||||
|
||||
pr_info("VFP support v0.3: ");
|
||||
if (VFP_arch)
|
||||
pr_cont("not present\n");
|
||||
else if (vfpsid & FPSID_NODOUBLE) {
|
||||
pr_cont("no double precision support\n");
|
||||
} else {
|
||||
hotcpu_notifier(vfp_hotplug, 0);
|
||||
|
||||
VFP_arch = (vfpsid & FPSID_ARCH_MASK) >> FPSID_ARCH_BIT; /* Extract the architecture version */
|
||||
pr_cont("implementor %02x architecture %d part %02x variant %x rev %x\n",
|
||||
(vfpsid & FPSID_IMPLEMENTER_MASK) >> FPSID_IMPLEMENTER_BIT,
|
||||
(vfpsid & FPSID_ARCH_MASK) >> FPSID_ARCH_BIT,
|
||||
(vfpsid & FPSID_PART_MASK) >> FPSID_PART_BIT,
|
||||
(vfpsid & FPSID_VARIANT_MASK) >> FPSID_VARIANT_BIT,
|
||||
(vfpsid & FPSID_REV_MASK) >> FPSID_REV_BIT);
|
||||
|
||||
vfp_vector = vfp_support_entry;
|
||||
|
||||
thread_register_notifier(&vfp_notifier_block);
|
||||
vfp_pm_init();
|
||||
|
||||
/*
|
||||
* We detected VFP, and the support code is
|
||||
* in place; report VFP support to userspace.
|
||||
*/
|
||||
elf_hwcap |= HWCAP_VFP;
|
||||
#ifdef CONFIG_VFPv3
|
||||
if (VFP_arch >= 2) {
|
||||
elf_hwcap |= HWCAP_VFPv3;
|
||||
|
||||
/*
|
||||
* Check for VFPv3 D16 and VFPv4 D16. CPUs in
|
||||
* this configuration only have 16 x 64bit
|
||||
* registers.
|
||||
*/
|
||||
if (((fmrx(MVFR0) & MVFR0_A_SIMD_MASK)) == 1)
|
||||
elf_hwcap |= HWCAP_VFPv3D16; /* also v4-D16 */
|
||||
else
|
||||
elf_hwcap |= HWCAP_VFPD32;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Check for the presence of the Advanced SIMD
|
||||
* load/store instructions, integer and single
|
||||
* precision floating point operations. Only check
|
||||
* for NEON if the hardware has the MVFR registers.
|
||||
*/
|
||||
if ((read_cpuid_id() & 0x000f0000) == 0x000f0000) {
|
||||
#ifdef CONFIG_NEON
|
||||
if ((fmrx(MVFR1) & 0x000fff00) == 0x00011100)
|
||||
elf_hwcap |= HWCAP_NEON;
|
||||
#endif
|
||||
#ifdef CONFIG_VFPv3
|
||||
if ((fmrx(MVFR1) & 0xf0000000) == 0x10000000)
|
||||
elf_hwcap |= HWCAP_VFPv4;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
core_initcall(vfp_init);
|
1246
arch/arm/vfp/vfpsingle.c
Normal file
1246
arch/arm/vfp/vfpsingle.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue