mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-27 06:34:11 +08:00
1bb50cad45
This ensures that we always notify context tracking that we
have exited from user space no matter how we enter the kernel.
It is similar to how arm64 handles context tracking, for example.
This allows the removal of all the exception_enter() calls that
were added in commit 49e4e15619
("tile: support CONTEXT_TRACKING and
thus NOHZ_FULL").
Signed-off-by: Chris Metcalf <cmetcalf@ezchip.com>
1596 lines
42 KiB
C
1596 lines
42 KiB
C
/*
|
|
* Copyright 2013 Tilera Corporation. All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, version 2.
|
|
*
|
|
* 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, GOOD TITLE or
|
|
* NON INFRINGEMENT. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* A code-rewriter that handles unaligned exception.
|
|
*/
|
|
|
|
#include <linux/smp.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/thread_info.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/mman.h>
|
|
#include <linux/types.h>
|
|
#include <linux/err.h>
|
|
#include <linux/module.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/prctl.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/traps.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/unaligned.h>
|
|
#include <arch/abi.h>
|
|
#include <arch/spr_def.h>
|
|
#include <arch/opcode.h>
|
|
|
|
|
|
/*
|
|
* This file handles unaligned exception for tile-Gx. The tilepro's unaligned
|
|
* exception is supported out of single_step.c
|
|
*/
|
|
|
|
int unaligned_printk;
|
|
|
|
static int __init setup_unaligned_printk(char *str)
|
|
{
|
|
long val;
|
|
if (kstrtol(str, 0, &val) != 0)
|
|
return 0;
|
|
unaligned_printk = val;
|
|
pr_info("Printk for each unaligned data accesses is %s\n",
|
|
unaligned_printk ? "enabled" : "disabled");
|
|
return 1;
|
|
}
|
|
__setup("unaligned_printk=", setup_unaligned_printk);
|
|
|
|
unsigned int unaligned_fixup_count;
|
|
|
|
#ifdef __tilegx__
|
|
|
|
/*
|
|
* Unalign data jit fixup code fragement. Reserved space is 128 bytes.
|
|
* The 1st 64-bit word saves fault PC address, 2nd word is the fault
|
|
* instruction bundle followed by 14 JIT bundles.
|
|
*/
|
|
|
|
struct unaligned_jit_fragment {
|
|
unsigned long pc;
|
|
tilegx_bundle_bits bundle;
|
|
tilegx_bundle_bits insn[14];
|
|
};
|
|
|
|
/*
|
|
* Check if a nop or fnop at bundle's pipeline X0.
|
|
*/
|
|
|
|
static bool is_bundle_x0_nop(tilegx_bundle_bits bundle)
|
|
{
|
|
return (((get_UnaryOpcodeExtension_X0(bundle) ==
|
|
NOP_UNARY_OPCODE_X0) &&
|
|
(get_RRROpcodeExtension_X0(bundle) ==
|
|
UNARY_RRR_0_OPCODE_X0) &&
|
|
(get_Opcode_X0(bundle) ==
|
|
RRR_0_OPCODE_X0)) ||
|
|
((get_UnaryOpcodeExtension_X0(bundle) ==
|
|
FNOP_UNARY_OPCODE_X0) &&
|
|
(get_RRROpcodeExtension_X0(bundle) ==
|
|
UNARY_RRR_0_OPCODE_X0) &&
|
|
(get_Opcode_X0(bundle) ==
|
|
RRR_0_OPCODE_X0)));
|
|
}
|
|
|
|
/*
|
|
* Check if nop or fnop at bundle's pipeline X1.
|
|
*/
|
|
|
|
static bool is_bundle_x1_nop(tilegx_bundle_bits bundle)
|
|
{
|
|
return (((get_UnaryOpcodeExtension_X1(bundle) ==
|
|
NOP_UNARY_OPCODE_X1) &&
|
|
(get_RRROpcodeExtension_X1(bundle) ==
|
|
UNARY_RRR_0_OPCODE_X1) &&
|
|
(get_Opcode_X1(bundle) ==
|
|
RRR_0_OPCODE_X1)) ||
|
|
((get_UnaryOpcodeExtension_X1(bundle) ==
|
|
FNOP_UNARY_OPCODE_X1) &&
|
|
(get_RRROpcodeExtension_X1(bundle) ==
|
|
UNARY_RRR_0_OPCODE_X1) &&
|
|
(get_Opcode_X1(bundle) ==
|
|
RRR_0_OPCODE_X1)));
|
|
}
|
|
|
|
/*
|
|
* Check if nop or fnop at bundle's Y0 pipeline.
|
|
*/
|
|
|
|
static bool is_bundle_y0_nop(tilegx_bundle_bits bundle)
|
|
{
|
|
return (((get_UnaryOpcodeExtension_Y0(bundle) ==
|
|
NOP_UNARY_OPCODE_Y0) &&
|
|
(get_RRROpcodeExtension_Y0(bundle) ==
|
|
UNARY_RRR_1_OPCODE_Y0) &&
|
|
(get_Opcode_Y0(bundle) ==
|
|
RRR_1_OPCODE_Y0)) ||
|
|
((get_UnaryOpcodeExtension_Y0(bundle) ==
|
|
FNOP_UNARY_OPCODE_Y0) &&
|
|
(get_RRROpcodeExtension_Y0(bundle) ==
|
|
UNARY_RRR_1_OPCODE_Y0) &&
|
|
(get_Opcode_Y0(bundle) ==
|
|
RRR_1_OPCODE_Y0)));
|
|
}
|
|
|
|
/*
|
|
* Check if nop or fnop at bundle's pipeline Y1.
|
|
*/
|
|
|
|
static bool is_bundle_y1_nop(tilegx_bundle_bits bundle)
|
|
{
|
|
return (((get_UnaryOpcodeExtension_Y1(bundle) ==
|
|
NOP_UNARY_OPCODE_Y1) &&
|
|
(get_RRROpcodeExtension_Y1(bundle) ==
|
|
UNARY_RRR_1_OPCODE_Y1) &&
|
|
(get_Opcode_Y1(bundle) ==
|
|
RRR_1_OPCODE_Y1)) ||
|
|
((get_UnaryOpcodeExtension_Y1(bundle) ==
|
|
FNOP_UNARY_OPCODE_Y1) &&
|
|
(get_RRROpcodeExtension_Y1(bundle) ==
|
|
UNARY_RRR_1_OPCODE_Y1) &&
|
|
(get_Opcode_Y1(bundle) ==
|
|
RRR_1_OPCODE_Y1)));
|
|
}
|
|
|
|
/*
|
|
* Test if a bundle's y0 and y1 pipelines are both nop or fnop.
|
|
*/
|
|
|
|
static bool is_y0_y1_nop(tilegx_bundle_bits bundle)
|
|
{
|
|
return is_bundle_y0_nop(bundle) && is_bundle_y1_nop(bundle);
|
|
}
|
|
|
|
/*
|
|
* Test if a bundle's x0 and x1 pipelines are both nop or fnop.
|
|
*/
|
|
|
|
static bool is_x0_x1_nop(tilegx_bundle_bits bundle)
|
|
{
|
|
return is_bundle_x0_nop(bundle) && is_bundle_x1_nop(bundle);
|
|
}
|
|
|
|
/*
|
|
* Find the destination, source registers of fault unalign access instruction
|
|
* at X1 or Y2. Also, allocate up to 3 scratch registers clob1, clob2 and
|
|
* clob3, which are guaranteed different from any register used in the fault
|
|
* bundle. r_alias is used to return if the other instructions other than the
|
|
* unalign load/store shares same register with ra, rb and rd.
|
|
*/
|
|
|
|
static void find_regs(tilegx_bundle_bits bundle, uint64_t *rd, uint64_t *ra,
|
|
uint64_t *rb, uint64_t *clob1, uint64_t *clob2,
|
|
uint64_t *clob3, bool *r_alias)
|
|
{
|
|
int i;
|
|
uint64_t reg;
|
|
uint64_t reg_map = 0, alias_reg_map = 0, map;
|
|
bool alias = false;
|
|
|
|
/*
|
|
* Parse fault bundle, find potential used registers and mark
|
|
* corresponding bits in reg_map and alias_map. These 2 bit maps
|
|
* are used to find the scratch registers and determine if there
|
|
* is register alais.
|
|
*/
|
|
if (bundle & TILEGX_BUNDLE_MODE_MASK) { /* Y Mode Bundle. */
|
|
|
|
reg = get_SrcA_Y2(bundle);
|
|
reg_map |= 1ULL << reg;
|
|
*ra = reg;
|
|
reg = get_SrcBDest_Y2(bundle);
|
|
reg_map |= 1ULL << reg;
|
|
|
|
if (rd) {
|
|
/* Load. */
|
|
*rd = reg;
|
|
alias_reg_map = (1ULL << *rd) | (1ULL << *ra);
|
|
} else {
|
|
/* Store. */
|
|
*rb = reg;
|
|
alias_reg_map = (1ULL << *ra) | (1ULL << *rb);
|
|
}
|
|
|
|
if (!is_bundle_y1_nop(bundle)) {
|
|
reg = get_SrcA_Y1(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map = (1ULL << reg);
|
|
|
|
reg = get_SrcB_Y1(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map |= (1ULL << reg);
|
|
|
|
reg = get_Dest_Y1(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map |= (1ULL << reg);
|
|
|
|
if (map & alias_reg_map)
|
|
alias = true;
|
|
}
|
|
|
|
if (!is_bundle_y0_nop(bundle)) {
|
|
reg = get_SrcA_Y0(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map = (1ULL << reg);
|
|
|
|
reg = get_SrcB_Y0(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map |= (1ULL << reg);
|
|
|
|
reg = get_Dest_Y0(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map |= (1ULL << reg);
|
|
|
|
if (map & alias_reg_map)
|
|
alias = true;
|
|
}
|
|
} else { /* X Mode Bundle. */
|
|
|
|
reg = get_SrcA_X1(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
*ra = reg;
|
|
if (rd) {
|
|
/* Load. */
|
|
reg = get_Dest_X1(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
*rd = reg;
|
|
alias_reg_map = (1ULL << *rd) | (1ULL << *ra);
|
|
} else {
|
|
/* Store. */
|
|
reg = get_SrcB_X1(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
*rb = reg;
|
|
alias_reg_map = (1ULL << *ra) | (1ULL << *rb);
|
|
}
|
|
|
|
if (!is_bundle_x0_nop(bundle)) {
|
|
reg = get_SrcA_X0(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map = (1ULL << reg);
|
|
|
|
reg = get_SrcB_X0(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map |= (1ULL << reg);
|
|
|
|
reg = get_Dest_X0(bundle);
|
|
reg_map |= (1ULL << reg);
|
|
map |= (1ULL << reg);
|
|
|
|
if (map & alias_reg_map)
|
|
alias = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "alias" indicates if the unalign access registers have collision
|
|
* with others in the same bundle. We jsut simply test all register
|
|
* operands case (RRR), ignored the case with immidate. If a bundle
|
|
* has no register alias, we may do fixup in a simple or fast manner.
|
|
* So if an immidata field happens to hit with a register, we may end
|
|
* up fall back to the generic handling.
|
|
*/
|
|
|
|
*r_alias = alias;
|
|
|
|
/* Flip bits on reg_map. */
|
|
reg_map ^= -1ULL;
|
|
|
|
/* Scan reg_map lower 54(TREG_SP) bits to find 3 set bits. */
|
|
for (i = 0; i < TREG_SP; i++) {
|
|
if (reg_map & (0x1ULL << i)) {
|
|
if (*clob1 == -1) {
|
|
*clob1 = i;
|
|
} else if (*clob2 == -1) {
|
|
*clob2 = i;
|
|
} else if (*clob3 == -1) {
|
|
*clob3 = i;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sanity check for register ra, rb, rd, clob1/2/3. Return true if any of them
|
|
* is unexpected.
|
|
*/
|
|
|
|
static bool check_regs(uint64_t rd, uint64_t ra, uint64_t rb,
|
|
uint64_t clob1, uint64_t clob2, uint64_t clob3)
|
|
{
|
|
bool unexpected = false;
|
|
if ((ra >= 56) && (ra != TREG_ZERO))
|
|
unexpected = true;
|
|
|
|
if ((clob1 >= 56) || (clob2 >= 56) || (clob3 >= 56))
|
|
unexpected = true;
|
|
|
|
if (rd != -1) {
|
|
if ((rd >= 56) && (rd != TREG_ZERO))
|
|
unexpected = true;
|
|
} else {
|
|
if ((rb >= 56) && (rb != TREG_ZERO))
|
|
unexpected = true;
|
|
}
|
|
return unexpected;
|
|
}
|
|
|
|
|
|
#define GX_INSN_X0_MASK ((1ULL << 31) - 1)
|
|
#define GX_INSN_X1_MASK (((1ULL << 31) - 1) << 31)
|
|
#define GX_INSN_Y0_MASK ((0xFULL << 27) | (0xFFFFFULL))
|
|
#define GX_INSN_Y1_MASK (GX_INSN_Y0_MASK << 31)
|
|
#define GX_INSN_Y2_MASK ((0x7FULL << 51) | (0x7FULL << 20))
|
|
|
|
#ifdef __LITTLE_ENDIAN
|
|
#define GX_INSN_BSWAP(_bundle_) (_bundle_)
|
|
#else
|
|
#define GX_INSN_BSWAP(_bundle_) swab64(_bundle_)
|
|
#endif /* __LITTLE_ENDIAN */
|
|
|
|
/*
|
|
* __JIT_CODE(.) creates template bundles in .rodata.unalign_data section.
|
|
* The corresponding static function jix_x#_###(.) generates partial or
|
|
* whole bundle based on the template and given arguments.
|
|
*/
|
|
|
|
#define __JIT_CODE(_X_) \
|
|
asm (".pushsection .rodata.unalign_data, \"a\"\n" \
|
|
_X_"\n" \
|
|
".popsection\n")
|
|
|
|
__JIT_CODE("__unalign_jit_x1_mtspr: {mtspr 0, r0}");
|
|
static tilegx_bundle_bits jit_x1_mtspr(int spr, int reg)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_mtspr;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_mtspr) & GX_INSN_X1_MASK) |
|
|
create_MT_Imm14_X1(spr) | create_SrcA_X1(reg);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_mfspr: {mfspr r0, 0}");
|
|
static tilegx_bundle_bits jit_x1_mfspr(int reg, int spr)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_mfspr;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_mfspr) & GX_INSN_X1_MASK) |
|
|
create_MF_Imm14_X1(spr) | create_Dest_X1(reg);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x0_addi: {addi r0, r0, 0; iret}");
|
|
static tilegx_bundle_bits jit_x0_addi(int rd, int ra, int imm8)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x0_addi;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x0_addi) & GX_INSN_X0_MASK) |
|
|
create_Dest_X0(rd) | create_SrcA_X0(ra) |
|
|
create_Imm8_X0(imm8);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_ldna: {ldna r0, r0}");
|
|
static tilegx_bundle_bits jit_x1_ldna(int rd, int ra)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_ldna;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_ldna) & GX_INSN_X1_MASK) |
|
|
create_Dest_X1(rd) | create_SrcA_X1(ra);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x0_dblalign: {dblalign r0, r0 ,r0}");
|
|
static tilegx_bundle_bits jit_x0_dblalign(int rd, int ra, int rb)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x0_dblalign;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x0_dblalign) & GX_INSN_X0_MASK) |
|
|
create_Dest_X0(rd) | create_SrcA_X0(ra) |
|
|
create_SrcB_X0(rb);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_iret: {iret}");
|
|
static tilegx_bundle_bits jit_x1_iret(void)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_iret;
|
|
return GX_INSN_BSWAP(__unalign_jit_x1_iret) & GX_INSN_X1_MASK;
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x01_fnop: {fnop;fnop}");
|
|
static tilegx_bundle_bits jit_x0_fnop(void)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x01_fnop;
|
|
return GX_INSN_BSWAP(__unalign_jit_x01_fnop) & GX_INSN_X0_MASK;
|
|
}
|
|
|
|
static tilegx_bundle_bits jit_x1_fnop(void)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x01_fnop;
|
|
return GX_INSN_BSWAP(__unalign_jit_x01_fnop) & GX_INSN_X1_MASK;
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_y2_dummy: {fnop; fnop; ld zero, sp}");
|
|
static tilegx_bundle_bits jit_y2_dummy(void)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_y2_dummy;
|
|
return GX_INSN_BSWAP(__unalign_jit_y2_dummy) & GX_INSN_Y2_MASK;
|
|
}
|
|
|
|
static tilegx_bundle_bits jit_y1_fnop(void)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_y2_dummy;
|
|
return GX_INSN_BSWAP(__unalign_jit_y2_dummy) & GX_INSN_Y1_MASK;
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_st1_add: {st1_add r1, r0, 0}");
|
|
static tilegx_bundle_bits jit_x1_st1_add(int ra, int rb, int imm8)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_st1_add;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_st1_add) &
|
|
(~create_SrcA_X1(-1)) &
|
|
GX_INSN_X1_MASK) | create_SrcA_X1(ra) |
|
|
create_SrcB_X1(rb) | create_Dest_Imm8_X1(imm8);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_st: {crc32_8 r1, r0, r0; st r0, r0}");
|
|
static tilegx_bundle_bits jit_x1_st(int ra, int rb)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_st;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_st) & GX_INSN_X1_MASK) |
|
|
create_SrcA_X1(ra) | create_SrcB_X1(rb);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_st_add: {st_add r1, r0, 0}");
|
|
static tilegx_bundle_bits jit_x1_st_add(int ra, int rb, int imm8)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_st_add;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_st_add) &
|
|
(~create_SrcA_X1(-1)) &
|
|
GX_INSN_X1_MASK) | create_SrcA_X1(ra) |
|
|
create_SrcB_X1(rb) | create_Dest_Imm8_X1(imm8);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_ld: {crc32_8 r1, r0, r0; ld r0, r0}");
|
|
static tilegx_bundle_bits jit_x1_ld(int rd, int ra)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_ld;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_ld) & GX_INSN_X1_MASK) |
|
|
create_Dest_X1(rd) | create_SrcA_X1(ra);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_ld_add: {ld_add r1, r0, 0}");
|
|
static tilegx_bundle_bits jit_x1_ld_add(int rd, int ra, int imm8)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_ld_add;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_ld_add) &
|
|
(~create_Dest_X1(-1)) &
|
|
GX_INSN_X1_MASK) | create_Dest_X1(rd) |
|
|
create_SrcA_X1(ra) | create_Imm8_X1(imm8);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x0_bfexts: {bfexts r0, r0, 0, 0}");
|
|
static tilegx_bundle_bits jit_x0_bfexts(int rd, int ra, int bfs, int bfe)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x0_bfexts;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x0_bfexts) &
|
|
GX_INSN_X0_MASK) |
|
|
create_Dest_X0(rd) | create_SrcA_X0(ra) |
|
|
create_BFStart_X0(bfs) | create_BFEnd_X0(bfe);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x0_bfextu: {bfextu r0, r0, 0, 0}");
|
|
static tilegx_bundle_bits jit_x0_bfextu(int rd, int ra, int bfs, int bfe)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x0_bfextu;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x0_bfextu) &
|
|
GX_INSN_X0_MASK) |
|
|
create_Dest_X0(rd) | create_SrcA_X0(ra) |
|
|
create_BFStart_X0(bfs) | create_BFEnd_X0(bfe);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_addi: {bfextu r1, r1, 0, 0; addi r0, r0, 0}");
|
|
static tilegx_bundle_bits jit_x1_addi(int rd, int ra, int imm8)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_addi;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_addi) & GX_INSN_X1_MASK) |
|
|
create_Dest_X1(rd) | create_SrcA_X1(ra) |
|
|
create_Imm8_X1(imm8);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x0_shrui: {shrui r0, r0, 0; iret}");
|
|
static tilegx_bundle_bits jit_x0_shrui(int rd, int ra, int imm6)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x0_shrui;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x0_shrui) &
|
|
GX_INSN_X0_MASK) |
|
|
create_Dest_X0(rd) | create_SrcA_X0(ra) |
|
|
create_ShAmt_X0(imm6);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x0_rotli: {rotli r0, r0, 0; iret}");
|
|
static tilegx_bundle_bits jit_x0_rotli(int rd, int ra, int imm6)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x0_rotli;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x0_rotli) &
|
|
GX_INSN_X0_MASK) |
|
|
create_Dest_X0(rd) | create_SrcA_X0(ra) |
|
|
create_ShAmt_X0(imm6);
|
|
}
|
|
|
|
__JIT_CODE("__unalign_jit_x1_bnezt: {bnezt r0, __unalign_jit_x1_bnezt}");
|
|
static tilegx_bundle_bits jit_x1_bnezt(int ra, int broff)
|
|
{
|
|
extern tilegx_bundle_bits __unalign_jit_x1_bnezt;
|
|
return (GX_INSN_BSWAP(__unalign_jit_x1_bnezt) &
|
|
GX_INSN_X1_MASK) |
|
|
create_SrcA_X1(ra) | create_BrOff_X1(broff);
|
|
}
|
|
|
|
#undef __JIT_CODE
|
|
|
|
/*
|
|
* This function generates unalign fixup JIT.
|
|
*
|
|
* We first find unalign load/store instruction's destination, source
|
|
* registers: ra, rb and rd. and 3 scratch registers by calling
|
|
* find_regs(...). 3 scratch clobbers should not alias with any register
|
|
* used in the fault bundle. Then analyze the fault bundle to determine
|
|
* if it's a load or store, operand width, branch or address increment etc.
|
|
* At last generated JIT is copied into JIT code area in user space.
|
|
*/
|
|
|
|
static
|
|
void jit_bundle_gen(struct pt_regs *regs, tilegx_bundle_bits bundle,
|
|
int align_ctl)
|
|
{
|
|
struct thread_info *info = current_thread_info();
|
|
struct unaligned_jit_fragment frag;
|
|
struct unaligned_jit_fragment *jit_code_area;
|
|
tilegx_bundle_bits bundle_2 = 0;
|
|
/* If bundle_2_enable = false, bundle_2 is fnop/nop operation. */
|
|
bool bundle_2_enable = true;
|
|
uint64_t ra = -1, rb = -1, rd = -1, clob1 = -1, clob2 = -1, clob3 = -1;
|
|
/*
|
|
* Indicate if the unalign access
|
|
* instruction's registers hit with
|
|
* others in the same bundle.
|
|
*/
|
|
bool alias = false;
|
|
bool load_n_store = true;
|
|
bool load_store_signed = false;
|
|
unsigned int load_store_size = 8;
|
|
bool y1_br = false; /* True, for a branch in same bundle at Y1.*/
|
|
int y1_br_reg = 0;
|
|
/* True for link operation. i.e. jalr or lnk at Y1 */
|
|
bool y1_lr = false;
|
|
int y1_lr_reg = 0;
|
|
bool x1_add = false;/* True, for load/store ADD instruction at X1*/
|
|
int x1_add_imm8 = 0;
|
|
bool unexpected = false;
|
|
int n = 0, k;
|
|
|
|
jit_code_area =
|
|
(struct unaligned_jit_fragment *)(info->unalign_jit_base);
|
|
|
|
memset((void *)&frag, 0, sizeof(frag));
|
|
|
|
/* 0: X mode, Otherwise: Y mode. */
|
|
if (bundle & TILEGX_BUNDLE_MODE_MASK) {
|
|
unsigned int mod, opcode;
|
|
|
|
if (get_Opcode_Y1(bundle) == RRR_1_OPCODE_Y1 &&
|
|
get_RRROpcodeExtension_Y1(bundle) ==
|
|
UNARY_RRR_1_OPCODE_Y1) {
|
|
|
|
opcode = get_UnaryOpcodeExtension_Y1(bundle);
|
|
|
|
/*
|
|
* Test "jalr", "jalrp", "jr", "jrp" instruction at Y1
|
|
* pipeline.
|
|
*/
|
|
switch (opcode) {
|
|
case JALR_UNARY_OPCODE_Y1:
|
|
case JALRP_UNARY_OPCODE_Y1:
|
|
y1_lr = true;
|
|
y1_lr_reg = 55; /* Link register. */
|
|
/* FALLTHROUGH */
|
|
case JR_UNARY_OPCODE_Y1:
|
|
case JRP_UNARY_OPCODE_Y1:
|
|
y1_br = true;
|
|
y1_br_reg = get_SrcA_Y1(bundle);
|
|
break;
|
|
case LNK_UNARY_OPCODE_Y1:
|
|
/* "lnk" at Y1 pipeline. */
|
|
y1_lr = true;
|
|
y1_lr_reg = get_Dest_Y1(bundle);
|
|
break;
|
|
}
|
|
}
|
|
|
|
opcode = get_Opcode_Y2(bundle);
|
|
mod = get_Mode(bundle);
|
|
|
|
/*
|
|
* bundle_2 is bundle after making Y2 as a dummy operation
|
|
* - ld zero, sp
|
|
*/
|
|
bundle_2 = (bundle & (~GX_INSN_Y2_MASK)) | jit_y2_dummy();
|
|
|
|
/* Make Y1 as fnop if Y1 is a branch or lnk operation. */
|
|
if (y1_br || y1_lr) {
|
|
bundle_2 &= ~(GX_INSN_Y1_MASK);
|
|
bundle_2 |= jit_y1_fnop();
|
|
}
|
|
|
|
if (is_y0_y1_nop(bundle_2))
|
|
bundle_2_enable = false;
|
|
|
|
if (mod == MODE_OPCODE_YC2) {
|
|
/* Store. */
|
|
load_n_store = false;
|
|
load_store_size = 1 << opcode;
|
|
load_store_signed = false;
|
|
find_regs(bundle, 0, &ra, &rb, &clob1, &clob2,
|
|
&clob3, &alias);
|
|
if (load_store_size > 8)
|
|
unexpected = true;
|
|
} else {
|
|
/* Load. */
|
|
load_n_store = true;
|
|
if (mod == MODE_OPCODE_YB2) {
|
|
switch (opcode) {
|
|
case LD_OPCODE_Y2:
|
|
load_store_signed = false;
|
|
load_store_size = 8;
|
|
break;
|
|
case LD4S_OPCODE_Y2:
|
|
load_store_signed = true;
|
|
load_store_size = 4;
|
|
break;
|
|
case LD4U_OPCODE_Y2:
|
|
load_store_signed = false;
|
|
load_store_size = 4;
|
|
break;
|
|
default:
|
|
unexpected = true;
|
|
}
|
|
} else if (mod == MODE_OPCODE_YA2) {
|
|
if (opcode == LD2S_OPCODE_Y2) {
|
|
load_store_signed = true;
|
|
load_store_size = 2;
|
|
} else if (opcode == LD2U_OPCODE_Y2) {
|
|
load_store_signed = false;
|
|
load_store_size = 2;
|
|
} else
|
|
unexpected = true;
|
|
} else
|
|
unexpected = true;
|
|
find_regs(bundle, &rd, &ra, &rb, &clob1, &clob2,
|
|
&clob3, &alias);
|
|
}
|
|
} else {
|
|
unsigned int opcode;
|
|
|
|
/* bundle_2 is bundle after making X1 as "fnop". */
|
|
bundle_2 = (bundle & (~GX_INSN_X1_MASK)) | jit_x1_fnop();
|
|
|
|
if (is_x0_x1_nop(bundle_2))
|
|
bundle_2_enable = false;
|
|
|
|
if (get_Opcode_X1(bundle) == RRR_0_OPCODE_X1) {
|
|
opcode = get_UnaryOpcodeExtension_X1(bundle);
|
|
|
|
if (get_RRROpcodeExtension_X1(bundle) ==
|
|
UNARY_RRR_0_OPCODE_X1) {
|
|
load_n_store = true;
|
|
find_regs(bundle, &rd, &ra, &rb, &clob1,
|
|
&clob2, &clob3, &alias);
|
|
|
|
switch (opcode) {
|
|
case LD_UNARY_OPCODE_X1:
|
|
load_store_signed = false;
|
|
load_store_size = 8;
|
|
break;
|
|
case LD4S_UNARY_OPCODE_X1:
|
|
load_store_signed = true;
|
|
/* FALLTHROUGH */
|
|
case LD4U_UNARY_OPCODE_X1:
|
|
load_store_size = 4;
|
|
break;
|
|
|
|
case LD2S_UNARY_OPCODE_X1:
|
|
load_store_signed = true;
|
|
/* FALLTHROUGH */
|
|
case LD2U_UNARY_OPCODE_X1:
|
|
load_store_size = 2;
|
|
break;
|
|
default:
|
|
unexpected = true;
|
|
}
|
|
} else {
|
|
load_n_store = false;
|
|
load_store_signed = false;
|
|
find_regs(bundle, 0, &ra, &rb,
|
|
&clob1, &clob2, &clob3,
|
|
&alias);
|
|
|
|
opcode = get_RRROpcodeExtension_X1(bundle);
|
|
switch (opcode) {
|
|
case ST_RRR_0_OPCODE_X1:
|
|
load_store_size = 8;
|
|
break;
|
|
case ST4_RRR_0_OPCODE_X1:
|
|
load_store_size = 4;
|
|
break;
|
|
case ST2_RRR_0_OPCODE_X1:
|
|
load_store_size = 2;
|
|
break;
|
|
default:
|
|
unexpected = true;
|
|
}
|
|
}
|
|
} else if (get_Opcode_X1(bundle) == IMM8_OPCODE_X1) {
|
|
load_n_store = true;
|
|
opcode = get_Imm8OpcodeExtension_X1(bundle);
|
|
switch (opcode) {
|
|
case LD_ADD_IMM8_OPCODE_X1:
|
|
load_store_size = 8;
|
|
break;
|
|
|
|
case LD4S_ADD_IMM8_OPCODE_X1:
|
|
load_store_signed = true;
|
|
/* FALLTHROUGH */
|
|
case LD4U_ADD_IMM8_OPCODE_X1:
|
|
load_store_size = 4;
|
|
break;
|
|
|
|
case LD2S_ADD_IMM8_OPCODE_X1:
|
|
load_store_signed = true;
|
|
/* FALLTHROUGH */
|
|
case LD2U_ADD_IMM8_OPCODE_X1:
|
|
load_store_size = 2;
|
|
break;
|
|
|
|
case ST_ADD_IMM8_OPCODE_X1:
|
|
load_n_store = false;
|
|
load_store_size = 8;
|
|
break;
|
|
case ST4_ADD_IMM8_OPCODE_X1:
|
|
load_n_store = false;
|
|
load_store_size = 4;
|
|
break;
|
|
case ST2_ADD_IMM8_OPCODE_X1:
|
|
load_n_store = false;
|
|
load_store_size = 2;
|
|
break;
|
|
default:
|
|
unexpected = true;
|
|
}
|
|
|
|
if (!unexpected) {
|
|
x1_add = true;
|
|
if (load_n_store)
|
|
x1_add_imm8 = get_Imm8_X1(bundle);
|
|
else
|
|
x1_add_imm8 = get_Dest_Imm8_X1(bundle);
|
|
}
|
|
|
|
find_regs(bundle, load_n_store ? (&rd) : NULL,
|
|
&ra, &rb, &clob1, &clob2, &clob3, &alias);
|
|
} else
|
|
unexpected = true;
|
|
}
|
|
|
|
/*
|
|
* Some sanity check for register numbers extracted from fault bundle.
|
|
*/
|
|
if (check_regs(rd, ra, rb, clob1, clob2, clob3) == true)
|
|
unexpected = true;
|
|
|
|
/* Give warning if register ra has an aligned address. */
|
|
if (!unexpected)
|
|
WARN_ON(!((load_store_size - 1) & (regs->regs[ra])));
|
|
|
|
|
|
/*
|
|
* Fault came from kernel space, here we only need take care of
|
|
* unaligned "get_user/put_user" macros defined in "uaccess.h".
|
|
* Basically, we will handle bundle like this:
|
|
* {ld/2u/4s rd, ra; movei rx, 0} or {st/2/4 ra, rb; movei rx, 0}
|
|
* (Refer to file "arch/tile/include/asm/uaccess.h" for details).
|
|
* For either load or store, byte-wise operation is performed by calling
|
|
* get_user() or put_user(). If the macro returns non-zero value,
|
|
* set the value to rx, otherwise set zero to rx. Finally make pc point
|
|
* to next bundle and return.
|
|
*/
|
|
|
|
if (EX1_PL(regs->ex1) != USER_PL) {
|
|
|
|
unsigned long rx = 0;
|
|
unsigned long x = 0, ret = 0;
|
|
|
|
if (y1_br || y1_lr || x1_add ||
|
|
(load_store_signed !=
|
|
(load_n_store && load_store_size == 4))) {
|
|
/* No branch, link, wrong sign-ext or load/store add. */
|
|
unexpected = true;
|
|
} else if (!unexpected) {
|
|
if (bundle & TILEGX_BUNDLE_MODE_MASK) {
|
|
/*
|
|
* Fault bundle is Y mode.
|
|
* Check if the Y1 and Y0 is the form of
|
|
* { movei rx, 0; nop/fnop }, if yes,
|
|
* find the rx.
|
|
*/
|
|
|
|
if ((get_Opcode_Y1(bundle) == ADDI_OPCODE_Y1)
|
|
&& (get_SrcA_Y1(bundle) == TREG_ZERO) &&
|
|
(get_Imm8_Y1(bundle) == 0) &&
|
|
is_bundle_y0_nop(bundle)) {
|
|
rx = get_Dest_Y1(bundle);
|
|
} else if ((get_Opcode_Y0(bundle) ==
|
|
ADDI_OPCODE_Y0) &&
|
|
(get_SrcA_Y0(bundle) == TREG_ZERO) &&
|
|
(get_Imm8_Y0(bundle) == 0) &&
|
|
is_bundle_y1_nop(bundle)) {
|
|
rx = get_Dest_Y0(bundle);
|
|
} else {
|
|
unexpected = true;
|
|
}
|
|
} else {
|
|
/*
|
|
* Fault bundle is X mode.
|
|
* Check if the X0 is 'movei rx, 0',
|
|
* if yes, find the rx.
|
|
*/
|
|
|
|
if ((get_Opcode_X0(bundle) == IMM8_OPCODE_X0)
|
|
&& (get_Imm8OpcodeExtension_X0(bundle) ==
|
|
ADDI_IMM8_OPCODE_X0) &&
|
|
(get_SrcA_X0(bundle) == TREG_ZERO) &&
|
|
(get_Imm8_X0(bundle) == 0)) {
|
|
rx = get_Dest_X0(bundle);
|
|
} else {
|
|
unexpected = true;
|
|
}
|
|
}
|
|
|
|
/* rx should be less than 56. */
|
|
if (!unexpected && (rx >= 56))
|
|
unexpected = true;
|
|
}
|
|
|
|
if (!search_exception_tables(regs->pc)) {
|
|
/* No fixup in the exception tables for the pc. */
|
|
unexpected = true;
|
|
}
|
|
|
|
if (unexpected) {
|
|
/* Unexpected unalign kernel fault. */
|
|
struct task_struct *tsk = validate_current();
|
|
|
|
bust_spinlocks(1);
|
|
|
|
show_regs(regs);
|
|
|
|
if (unlikely(tsk->pid < 2)) {
|
|
panic("Kernel unalign fault running %s!",
|
|
tsk->pid ? "init" : "the idle task");
|
|
}
|
|
#ifdef SUPPORT_DIE
|
|
die("Oops", regs);
|
|
#endif
|
|
bust_spinlocks(1);
|
|
|
|
do_group_exit(SIGKILL);
|
|
|
|
} else {
|
|
unsigned long i, b = 0;
|
|
unsigned char *ptr =
|
|
(unsigned char *)regs->regs[ra];
|
|
if (load_n_store) {
|
|
/* handle get_user(x, ptr) */
|
|
for (i = 0; i < load_store_size; i++) {
|
|
ret = get_user(b, ptr++);
|
|
if (!ret) {
|
|
/* Success! update x. */
|
|
#ifdef __LITTLE_ENDIAN
|
|
x |= (b << (8 * i));
|
|
#else
|
|
x <<= 8;
|
|
x |= b;
|
|
#endif /* __LITTLE_ENDIAN */
|
|
} else {
|
|
x = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Sign-extend 4-byte loads. */
|
|
if (load_store_size == 4)
|
|
x = (long)(int)x;
|
|
|
|
/* Set register rd. */
|
|
regs->regs[rd] = x;
|
|
|
|
/* Set register rx. */
|
|
regs->regs[rx] = ret;
|
|
|
|
/* Bump pc. */
|
|
regs->pc += 8;
|
|
|
|
} else {
|
|
/* Handle put_user(x, ptr) */
|
|
x = regs->regs[rb];
|
|
#ifdef __LITTLE_ENDIAN
|
|
b = x;
|
|
#else
|
|
/*
|
|
* Swap x in order to store x from low
|
|
* to high memory same as the
|
|
* little-endian case.
|
|
*/
|
|
switch (load_store_size) {
|
|
case 8:
|
|
b = swab64(x);
|
|
break;
|
|
case 4:
|
|
b = swab32(x);
|
|
break;
|
|
case 2:
|
|
b = swab16(x);
|
|
break;
|
|
}
|
|
#endif /* __LITTLE_ENDIAN */
|
|
for (i = 0; i < load_store_size; i++) {
|
|
ret = put_user(b, ptr++);
|
|
if (ret)
|
|
break;
|
|
/* Success! shift 1 byte. */
|
|
b >>= 8;
|
|
}
|
|
/* Set register rx. */
|
|
regs->regs[rx] = ret;
|
|
|
|
/* Bump pc. */
|
|
regs->pc += 8;
|
|
}
|
|
}
|
|
|
|
unaligned_fixup_count++;
|
|
|
|
if (unaligned_printk) {
|
|
pr_info("%s/%d - Unalign fixup for kernel access to userspace %lx\n",
|
|
current->comm, current->pid, regs->regs[ra]);
|
|
}
|
|
|
|
/* Done! Return to the exception handler. */
|
|
return;
|
|
}
|
|
|
|
if ((align_ctl == 0) || unexpected) {
|
|
siginfo_t info = {
|
|
.si_signo = SIGBUS,
|
|
.si_code = BUS_ADRALN,
|
|
.si_addr = (unsigned char __user *)0
|
|
};
|
|
if (unaligned_printk)
|
|
pr_info("Unalign bundle: unexp @%llx, %llx\n",
|
|
(unsigned long long)regs->pc,
|
|
(unsigned long long)bundle);
|
|
|
|
if (ra < 56) {
|
|
unsigned long uaa = (unsigned long)regs->regs[ra];
|
|
/* Set bus Address. */
|
|
info.si_addr = (unsigned char __user *)uaa;
|
|
}
|
|
|
|
unaligned_fixup_count++;
|
|
|
|
trace_unhandled_signal("unaligned fixup trap", regs,
|
|
(unsigned long)info.si_addr, SIGBUS);
|
|
force_sig_info(info.si_signo, &info, current);
|
|
return;
|
|
}
|
|
|
|
#ifdef __LITTLE_ENDIAN
|
|
#define UA_FIXUP_ADDR_DELTA 1
|
|
#define UA_FIXUP_BFEXT_START(_B_) 0
|
|
#define UA_FIXUP_BFEXT_END(_B_) (8 * (_B_) - 1)
|
|
#else /* __BIG_ENDIAN */
|
|
#define UA_FIXUP_ADDR_DELTA -1
|
|
#define UA_FIXUP_BFEXT_START(_B_) (64 - 8 * (_B_))
|
|
#define UA_FIXUP_BFEXT_END(_B_) 63
|
|
#endif /* __LITTLE_ENDIAN */
|
|
|
|
|
|
|
|
if ((ra != rb) && (rd != TREG_SP) && !alias &&
|
|
!y1_br && !y1_lr && !x1_add) {
|
|
/*
|
|
* Simple case: ra != rb and no register alias found,
|
|
* and no branch or link. This will be the majority.
|
|
* We can do a little better for simplae case than the
|
|
* generic scheme below.
|
|
*/
|
|
if (!load_n_store) {
|
|
/*
|
|
* Simple store: ra != rb, no need for scratch register.
|
|
* Just store and rotate to right bytewise.
|
|
*/
|
|
#ifdef __BIG_ENDIAN
|
|
frag.insn[n++] =
|
|
jit_x0_addi(ra, ra, load_store_size - 1) |
|
|
jit_x1_fnop();
|
|
#endif /* __BIG_ENDIAN */
|
|
for (k = 0; k < load_store_size; k++) {
|
|
/* Store a byte. */
|
|
frag.insn[n++] =
|
|
jit_x0_rotli(rb, rb, 56) |
|
|
jit_x1_st1_add(ra, rb,
|
|
UA_FIXUP_ADDR_DELTA);
|
|
}
|
|
#ifdef __BIG_ENDIAN
|
|
frag.insn[n] = jit_x1_addi(ra, ra, 1);
|
|
#else
|
|
frag.insn[n] = jit_x1_addi(ra, ra,
|
|
-1 * load_store_size);
|
|
#endif /* __LITTLE_ENDIAN */
|
|
|
|
if (load_store_size == 8) {
|
|
frag.insn[n] |= jit_x0_fnop();
|
|
} else if (load_store_size == 4) {
|
|
frag.insn[n] |= jit_x0_rotli(rb, rb, 32);
|
|
} else { /* = 2 */
|
|
frag.insn[n] |= jit_x0_rotli(rb, rb, 16);
|
|
}
|
|
n++;
|
|
if (bundle_2_enable)
|
|
frag.insn[n++] = bundle_2;
|
|
frag.insn[n++] = jit_x0_fnop() | jit_x1_iret();
|
|
} else {
|
|
if (rd == ra) {
|
|
/* Use two clobber registers: clob1/2. */
|
|
frag.insn[n++] =
|
|
jit_x0_addi(TREG_SP, TREG_SP, -16) |
|
|
jit_x1_fnop();
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob1, ra, 7) |
|
|
jit_x1_st_add(TREG_SP, clob1, -8);
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob2, ra, 0) |
|
|
jit_x1_st(TREG_SP, clob2);
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ldna(rd, ra);
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ldna(clob1, clob1);
|
|
/*
|
|
* Note: we must make sure that rd must not
|
|
* be sp. Recover clob1/2 from stack.
|
|
*/
|
|
frag.insn[n++] =
|
|
jit_x0_dblalign(rd, clob1, clob2) |
|
|
jit_x1_ld_add(clob2, TREG_SP, 8);
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ld_add(clob1, TREG_SP, 16);
|
|
} else {
|
|
/* Use one clobber register: clob1 only. */
|
|
frag.insn[n++] =
|
|
jit_x0_addi(TREG_SP, TREG_SP, -16) |
|
|
jit_x1_fnop();
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob1, ra, 7) |
|
|
jit_x1_st(TREG_SP, clob1);
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ldna(rd, ra);
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ldna(clob1, clob1);
|
|
/*
|
|
* Note: we must make sure that rd must not
|
|
* be sp. Recover clob1 from stack.
|
|
*/
|
|
frag.insn[n++] =
|
|
jit_x0_dblalign(rd, clob1, ra) |
|
|
jit_x1_ld_add(clob1, TREG_SP, 16);
|
|
}
|
|
|
|
if (bundle_2_enable)
|
|
frag.insn[n++] = bundle_2;
|
|
/*
|
|
* For non 8-byte load, extract corresponding bytes and
|
|
* signed extension.
|
|
*/
|
|
if (load_store_size == 4) {
|
|
if (load_store_signed)
|
|
frag.insn[n++] =
|
|
jit_x0_bfexts(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(4),
|
|
UA_FIXUP_BFEXT_END(4)) |
|
|
jit_x1_fnop();
|
|
else
|
|
frag.insn[n++] =
|
|
jit_x0_bfextu(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(4),
|
|
UA_FIXUP_BFEXT_END(4)) |
|
|
jit_x1_fnop();
|
|
} else if (load_store_size == 2) {
|
|
if (load_store_signed)
|
|
frag.insn[n++] =
|
|
jit_x0_bfexts(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(2),
|
|
UA_FIXUP_BFEXT_END(2)) |
|
|
jit_x1_fnop();
|
|
else
|
|
frag.insn[n++] =
|
|
jit_x0_bfextu(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(2),
|
|
UA_FIXUP_BFEXT_END(2)) |
|
|
jit_x1_fnop();
|
|
}
|
|
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_iret();
|
|
}
|
|
} else if (!load_n_store) {
|
|
|
|
/*
|
|
* Generic memory store cases: use 3 clobber registers.
|
|
*
|
|
* Alloc space for saveing clob2,1,3 on user's stack.
|
|
* register clob3 points to where clob2 saved, followed by
|
|
* clob1 and 3 from high to low memory.
|
|
*/
|
|
frag.insn[n++] =
|
|
jit_x0_addi(TREG_SP, TREG_SP, -32) |
|
|
jit_x1_fnop();
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob3, TREG_SP, 16) |
|
|
jit_x1_st_add(TREG_SP, clob3, 8);
|
|
#ifdef __LITTLE_ENDIAN
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob1, ra, 0) |
|
|
jit_x1_st_add(TREG_SP, clob1, 8);
|
|
#else
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob1, ra, load_store_size - 1) |
|
|
jit_x1_st_add(TREG_SP, clob1, 8);
|
|
#endif
|
|
if (load_store_size == 8) {
|
|
/*
|
|
* We save one byte a time, not for fast, but compact
|
|
* code. After each store, data source register shift
|
|
* right one byte. unchanged after 8 stores.
|
|
*/
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob2, TREG_ZERO, 7) |
|
|
jit_x1_st_add(TREG_SP, clob2, 16);
|
|
frag.insn[n++] =
|
|
jit_x0_rotli(rb, rb, 56) |
|
|
jit_x1_st1_add(clob1, rb, UA_FIXUP_ADDR_DELTA);
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob2, clob2, -1) |
|
|
jit_x1_bnezt(clob2, -1);
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_addi(clob2, y1_br_reg, 0);
|
|
} else if (load_store_size == 4) {
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob2, TREG_ZERO, 3) |
|
|
jit_x1_st_add(TREG_SP, clob2, 16);
|
|
frag.insn[n++] =
|
|
jit_x0_rotli(rb, rb, 56) |
|
|
jit_x1_st1_add(clob1, rb, UA_FIXUP_ADDR_DELTA);
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob2, clob2, -1) |
|
|
jit_x1_bnezt(clob2, -1);
|
|
/*
|
|
* same as 8-byte case, but need shift another 4
|
|
* byte to recover rb for 4-byte store.
|
|
*/
|
|
frag.insn[n++] = jit_x0_rotli(rb, rb, 32) |
|
|
jit_x1_addi(clob2, y1_br_reg, 0);
|
|
} else { /* =2 */
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob2, rb, 0) |
|
|
jit_x1_st_add(TREG_SP, clob2, 16);
|
|
for (k = 0; k < 2; k++) {
|
|
frag.insn[n++] =
|
|
jit_x0_shrui(rb, rb, 8) |
|
|
jit_x1_st1_add(clob1, rb,
|
|
UA_FIXUP_ADDR_DELTA);
|
|
}
|
|
frag.insn[n++] =
|
|
jit_x0_addi(rb, clob2, 0) |
|
|
jit_x1_addi(clob2, y1_br_reg, 0);
|
|
}
|
|
|
|
if (bundle_2_enable)
|
|
frag.insn[n++] = bundle_2;
|
|
|
|
if (y1_lr) {
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_mfspr(y1_lr_reg,
|
|
SPR_EX_CONTEXT_0_0);
|
|
}
|
|
if (y1_br) {
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_mtspr(SPR_EX_CONTEXT_0_0,
|
|
clob2);
|
|
}
|
|
if (x1_add) {
|
|
frag.insn[n++] =
|
|
jit_x0_addi(ra, ra, x1_add_imm8) |
|
|
jit_x1_ld_add(clob2, clob3, -8);
|
|
} else {
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ld_add(clob2, clob3, -8);
|
|
}
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ld_add(clob1, clob3, -8);
|
|
frag.insn[n++] = jit_x0_fnop() | jit_x1_ld(clob3, clob3);
|
|
frag.insn[n++] = jit_x0_fnop() | jit_x1_iret();
|
|
|
|
} else {
|
|
/*
|
|
* Generic memory load cases.
|
|
*
|
|
* Alloc space for saveing clob1,2,3 on user's stack.
|
|
* register clob3 points to where clob1 saved, followed
|
|
* by clob2 and 3 from high to low memory.
|
|
*/
|
|
|
|
frag.insn[n++] =
|
|
jit_x0_addi(TREG_SP, TREG_SP, -32) |
|
|
jit_x1_fnop();
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob3, TREG_SP, 16) |
|
|
jit_x1_st_add(TREG_SP, clob3, 8);
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob2, ra, 0) |
|
|
jit_x1_st_add(TREG_SP, clob2, 8);
|
|
|
|
if (y1_br) {
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob1, y1_br_reg, 0) |
|
|
jit_x1_st_add(TREG_SP, clob1, 16);
|
|
} else {
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_st_add(TREG_SP, clob1, 16);
|
|
}
|
|
|
|
if (bundle_2_enable)
|
|
frag.insn[n++] = bundle_2;
|
|
|
|
if (y1_lr) {
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_mfspr(y1_lr_reg,
|
|
SPR_EX_CONTEXT_0_0);
|
|
}
|
|
|
|
if (y1_br) {
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_mtspr(SPR_EX_CONTEXT_0_0,
|
|
clob1);
|
|
}
|
|
|
|
frag.insn[n++] =
|
|
jit_x0_addi(clob1, clob2, 7) |
|
|
jit_x1_ldna(rd, clob2);
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ldna(clob1, clob1);
|
|
frag.insn[n++] =
|
|
jit_x0_dblalign(rd, clob1, clob2) |
|
|
jit_x1_ld_add(clob1, clob3, -8);
|
|
if (x1_add) {
|
|
frag.insn[n++] =
|
|
jit_x0_addi(ra, ra, x1_add_imm8) |
|
|
jit_x1_ld_add(clob2, clob3, -8);
|
|
} else {
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ld_add(clob2, clob3, -8);
|
|
}
|
|
|
|
frag.insn[n++] =
|
|
jit_x0_fnop() |
|
|
jit_x1_ld(clob3, clob3);
|
|
|
|
if (load_store_size == 4) {
|
|
if (load_store_signed)
|
|
frag.insn[n++] =
|
|
jit_x0_bfexts(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(4),
|
|
UA_FIXUP_BFEXT_END(4)) |
|
|
jit_x1_fnop();
|
|
else
|
|
frag.insn[n++] =
|
|
jit_x0_bfextu(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(4),
|
|
UA_FIXUP_BFEXT_END(4)) |
|
|
jit_x1_fnop();
|
|
} else if (load_store_size == 2) {
|
|
if (load_store_signed)
|
|
frag.insn[n++] =
|
|
jit_x0_bfexts(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(2),
|
|
UA_FIXUP_BFEXT_END(2)) |
|
|
jit_x1_fnop();
|
|
else
|
|
frag.insn[n++] =
|
|
jit_x0_bfextu(
|
|
rd, rd,
|
|
UA_FIXUP_BFEXT_START(2),
|
|
UA_FIXUP_BFEXT_END(2)) |
|
|
jit_x1_fnop();
|
|
}
|
|
|
|
frag.insn[n++] = jit_x0_fnop() | jit_x1_iret();
|
|
}
|
|
|
|
/* Max JIT bundle count is 14. */
|
|
WARN_ON(n > 14);
|
|
|
|
if (!unexpected) {
|
|
int status = 0;
|
|
int idx = (regs->pc >> 3) &
|
|
((1ULL << (PAGE_SHIFT - UNALIGN_JIT_SHIFT)) - 1);
|
|
|
|
frag.pc = regs->pc;
|
|
frag.bundle = bundle;
|
|
|
|
if (unaligned_printk) {
|
|
pr_info("%s/%d, Unalign fixup: pc=%lx bundle=%lx %d %d %d %d %d %d %d %d\n",
|
|
current->comm, current->pid,
|
|
(unsigned long)frag.pc,
|
|
(unsigned long)frag.bundle,
|
|
(int)alias, (int)rd, (int)ra,
|
|
(int)rb, (int)bundle_2_enable,
|
|
(int)y1_lr, (int)y1_br, (int)x1_add);
|
|
|
|
for (k = 0; k < n; k += 2)
|
|
pr_info("[%d] %016llx %016llx\n",
|
|
k, (unsigned long long)frag.insn[k],
|
|
(unsigned long long)frag.insn[k+1]);
|
|
}
|
|
|
|
/* Swap bundle byte order for big endian sys. */
|
|
#ifdef __BIG_ENDIAN
|
|
frag.bundle = GX_INSN_BSWAP(frag.bundle);
|
|
for (k = 0; k < n; k++)
|
|
frag.insn[k] = GX_INSN_BSWAP(frag.insn[k]);
|
|
#endif /* __BIG_ENDIAN */
|
|
|
|
status = copy_to_user((void __user *)&jit_code_area[idx],
|
|
&frag, sizeof(frag));
|
|
if (status) {
|
|
/* Fail to copy JIT into user land. send SIGSEGV. */
|
|
siginfo_t info = {
|
|
.si_signo = SIGSEGV,
|
|
.si_code = SEGV_MAPERR,
|
|
.si_addr = (void __user *)&jit_code_area[idx]
|
|
};
|
|
|
|
pr_warn("Unalign fixup: pid=%d %s jit_code_area=%llx\n",
|
|
current->pid, current->comm,
|
|
(unsigned long long)&jit_code_area[idx]);
|
|
|
|
trace_unhandled_signal("segfault in unalign fixup",
|
|
regs,
|
|
(unsigned long)info.si_addr,
|
|
SIGSEGV);
|
|
force_sig_info(info.si_signo, &info, current);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Do a cheaper increment, not accurate. */
|
|
unaligned_fixup_count++;
|
|
__flush_icache_range((unsigned long)&jit_code_area[idx],
|
|
(unsigned long)&jit_code_area[idx] +
|
|
sizeof(frag));
|
|
|
|
/* Setup SPR_EX_CONTEXT_0_0/1 for returning to user program.*/
|
|
__insn_mtspr(SPR_EX_CONTEXT_0_0, regs->pc + 8);
|
|
__insn_mtspr(SPR_EX_CONTEXT_0_1, PL_ICS_EX1(USER_PL, 0));
|
|
|
|
/* Modify pc at the start of new JIT. */
|
|
regs->pc = (unsigned long)&jit_code_area[idx].insn[0];
|
|
/* Set ICS in SPR_EX_CONTEXT_K_1. */
|
|
regs->ex1 = PL_ICS_EX1(USER_PL, 1);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* C function to generate unalign data JIT. Called from unalign data
|
|
* interrupt handler.
|
|
*
|
|
* First check if unalign fix is disabled or exception did not not come from
|
|
* user space or sp register points to unalign address, if true, generate a
|
|
* SIGBUS. Then map a page into user space as JIT area if it is not mapped
|
|
* yet. Genenerate JIT code by calling jit_bundle_gen(). After that return
|
|
* back to exception handler.
|
|
*
|
|
* The exception handler will "iret" to new generated JIT code after
|
|
* restoring caller saved registers. In theory, the JIT code will perform
|
|
* another "iret" to resume user's program.
|
|
*/
|
|
|
|
void do_unaligned(struct pt_regs *regs, int vecnum)
|
|
{
|
|
tilegx_bundle_bits __user *pc;
|
|
tilegx_bundle_bits bundle;
|
|
struct thread_info *info = current_thread_info();
|
|
int align_ctl;
|
|
|
|
/* Checks the per-process unaligned JIT flags */
|
|
align_ctl = unaligned_fixup;
|
|
switch (task_thread_info(current)->align_ctl) {
|
|
case PR_UNALIGN_NOPRINT:
|
|
align_ctl = 1;
|
|
break;
|
|
case PR_UNALIGN_SIGBUS:
|
|
align_ctl = 0;
|
|
break;
|
|
}
|
|
|
|
/* Enable iterrupt in order to access user land. */
|
|
local_irq_enable();
|
|
|
|
/*
|
|
* The fault came from kernel space. Two choices:
|
|
* (a) unaligned_fixup < 1, we will first call get/put_user fixup
|
|
* to return -EFAULT. If no fixup, simply panic the kernel.
|
|
* (b) unaligned_fixup >=1, we will try to fix the unaligned access
|
|
* if it was triggered by get_user/put_user() macros. Panic the
|
|
* kernel if it is not fixable.
|
|
*/
|
|
|
|
if (EX1_PL(regs->ex1) != USER_PL) {
|
|
|
|
if (align_ctl < 1) {
|
|
unaligned_fixup_count++;
|
|
/* If exception came from kernel, try fix it up. */
|
|
if (fixup_exception(regs)) {
|
|
if (unaligned_printk)
|
|
pr_info("Unalign fixup: %d %llx @%llx\n",
|
|
(int)unaligned_fixup,
|
|
(unsigned long long)regs->ex1,
|
|
(unsigned long long)regs->pc);
|
|
} else {
|
|
/* Not fixable. Go panic. */
|
|
panic("Unalign exception in Kernel. pc=%lx",
|
|
regs->pc);
|
|
}
|
|
} else {
|
|
/*
|
|
* Try to fix the exception. If we can't, panic the
|
|
* kernel.
|
|
*/
|
|
bundle = GX_INSN_BSWAP(
|
|
*((tilegx_bundle_bits *)(regs->pc)));
|
|
jit_bundle_gen(regs, bundle, align_ctl);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Fault came from user with ICS or stack is not aligned.
|
|
* If so, we will trigger SIGBUS.
|
|
*/
|
|
if ((regs->sp & 0x7) || (regs->ex1) || (align_ctl < 0)) {
|
|
siginfo_t info = {
|
|
.si_signo = SIGBUS,
|
|
.si_code = BUS_ADRALN,
|
|
.si_addr = (unsigned char __user *)0
|
|
};
|
|
|
|
if (unaligned_printk)
|
|
pr_info("Unalign fixup: %d %llx @%llx\n",
|
|
(int)unaligned_fixup,
|
|
(unsigned long long)regs->ex1,
|
|
(unsigned long long)regs->pc);
|
|
|
|
unaligned_fixup_count++;
|
|
|
|
trace_unhandled_signal("unaligned fixup trap", regs, 0, SIGBUS);
|
|
force_sig_info(info.si_signo, &info, current);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Read the bundle casued the exception! */
|
|
pc = (tilegx_bundle_bits __user *)(regs->pc);
|
|
if (get_user(bundle, pc) != 0) {
|
|
/* Probably never be here since pc is valid user address.*/
|
|
siginfo_t info = {
|
|
.si_signo = SIGSEGV,
|
|
.si_code = SEGV_MAPERR,
|
|
.si_addr = (void __user *)pc
|
|
};
|
|
pr_err("Couldn't read instruction at %p trying to step\n", pc);
|
|
trace_unhandled_signal("segfault in unalign fixup", regs,
|
|
(unsigned long)info.si_addr, SIGSEGV);
|
|
force_sig_info(info.si_signo, &info, current);
|
|
return;
|
|
}
|
|
|
|
if (!info->unalign_jit_base) {
|
|
void __user *user_page;
|
|
|
|
/*
|
|
* Allocate a page in userland.
|
|
* For 64-bit processes we try to place the mapping far
|
|
* from anything else that might be going on (specifically
|
|
* 64 GB below the top of the user address space). If it
|
|
* happens not to be possible to put it there, it's OK;
|
|
* the kernel will choose another location and we'll
|
|
* remember it for later.
|
|
*/
|
|
if (is_compat_task())
|
|
user_page = NULL;
|
|
else
|
|
user_page = (void __user *)(TASK_SIZE - (1UL << 36)) +
|
|
(current->pid << PAGE_SHIFT);
|
|
|
|
user_page = (void __user *) vm_mmap(NULL,
|
|
(unsigned long)user_page,
|
|
PAGE_SIZE,
|
|
PROT_EXEC | PROT_READ |
|
|
PROT_WRITE,
|
|
#ifdef CONFIG_HOMECACHE
|
|
MAP_CACHE_HOME_TASK |
|
|
#endif
|
|
MAP_PRIVATE |
|
|
MAP_ANONYMOUS,
|
|
0);
|
|
|
|
if (IS_ERR((void __force *)user_page)) {
|
|
pr_err("Out of kernel pages trying do_mmap\n");
|
|
return;
|
|
}
|
|
|
|
/* Save the address in the thread_info struct */
|
|
info->unalign_jit_base = user_page;
|
|
if (unaligned_printk)
|
|
pr_info("Unalign bundle: %d:%d, allocate page @%llx\n",
|
|
raw_smp_processor_id(), current->pid,
|
|
(unsigned long long)user_page);
|
|
}
|
|
|
|
/* Generate unalign JIT */
|
|
jit_bundle_gen(regs, GX_INSN_BSWAP(bundle), align_ctl);
|
|
}
|
|
|
|
#endif /* __tilegx__ */
|