1 /*
2   * Copyright (C) 2017 ARM Ltd.
3   *
4   * This program is free software; you can redistribute it and/or modify
5   * it under the terms of the GNU General Public License version 2 as
6   * published by the Free Software Foundation.
7   *
8   * This program is distributed in the hope that it will be useful,
9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   * GNU General Public License for more details.
12   *
13   * You should have received a copy of the GNU General Public License
14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15   */
16 #include <xen/lib.h>
17 #include <xen/bitops.h>
18 #include <xen/sizes.h>
19 #include <asm/insn.h>
20 
21 /* Mask of branch instructions' immediate. */
22 #define BRANCH_INSN_IMM_MASK    GENMASK(23, 0)
23 /* Shift of branch instructions' immediate. */
24 #define BRANCH_INSN_IMM_SHIFT   0
25 
branch_insn_encode_immediate(uint32_t insn,int32_t offset)26 static uint32_t branch_insn_encode_immediate(uint32_t insn, int32_t offset)
27 {
28     uint32_t imm;
29 
30     /*
31      * Encode the offset to imm. All ARM32 instructions must be word aligned.
32      * Therefore the offset value's bits [1:0] equal to zero.
33      * (see ARM DDI 0406C.c A8.8.18/A8.8.25 for more encode/decode details
34      * about ARM32 branch instructions)
35      */
36     imm = ((offset >> 2) & BRANCH_INSN_IMM_MASK) << BRANCH_INSN_IMM_SHIFT;
37 
38     /* Update the immediate field. */
39     insn &= ~(BRANCH_INSN_IMM_MASK << BRANCH_INSN_IMM_SHIFT);
40     insn |= imm;
41 
42     return insn;
43 }
44 
45 /*
46  * Decode the branch offset from a branch instruction's imm field.
47  * The branch offset is a signed value, so it can be used to compute
48  * a new branch target.
49  */
aarch32_get_branch_offset(uint32_t insn)50 int32_t aarch32_get_branch_offset(uint32_t insn)
51 {
52     uint32_t imm;
53 
54     /* Retrieve imm from branch instruction. */
55     imm = ( insn >> BRANCH_INSN_IMM_SHIFT ) & BRANCH_INSN_IMM_MASK;
56 
57     /*
58      * Check the imm signed bit. If the imm is a negative value, we
59      * have to extend the imm to a full 32 bit negative value.
60      */
61     if ( imm & BIT(23) )
62         imm |= GENMASK(31, 24);
63 
64     return (int32_t)(imm << 2);
65 }
66 
67 /*
68  * Encode the displacement of a branch in the imm field and return the
69  * updated instruction.
70  */
aarch32_set_branch_offset(uint32_t insn,int32_t offset)71 uint32_t aarch32_set_branch_offset(uint32_t insn, int32_t offset)
72 {
73     /* B/BL support [-32M, 32M) offset (see ARM DDI 0406C.c A4.3). */
74     if ( offset < -SZ_32M || offset >= SZ_32M )
75     {
76         printk(XENLOG_ERR
77                "%s: new branch offset out of range.\n", __func__);
78         return BUG_OPCODE;
79     }
80 
81     return branch_insn_encode_immediate(insn, offset);
82 }
83 
84 /*
85  * Local variables:
86  * mode: C
87  * c-file-style: "BSD"
88  * c-basic-offset: 4
89  * indent-tabs-mode: nil
90  * End:
91  */
92