1 /*        $NetBSD: fixup.c,v 1.13 2022/01/01 01:15:11 macallan Exp $  */
2 /*-
3  * Copyright (c) 2010, 2011 The NetBSD Foundation, Inc.
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Raytheon BBN Technologies Corp and Defense Advanced Research Projects
8  * Agency and which was developed by Matt Thomas of 3am Software Foundry.
9  *
10  * This material is based upon work supported by the Defense Advanced Research
11  * Projects Agency and Space and Naval Warfare Systems Center, Pacific, under
12  * Contract No. N66001-09-C-2073.
13  * Approved for Public Release, Distribution Unlimited
14  *
15  * Redistribution and use in source and binary forms, with or without
16  * modification, are permitted provided that the following conditions
17  * are met:
18  * 1. Redistributions of source code must retain the above copyright
19  *    notice, this list of conditions and the following disclaimer.
20  * 2. Redistributions in binary form must reproduce the above copyright
21  *    notice, this list of conditions and the following disclaimer in the
22  *    documentation and/or other materials provided with the distribution.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
25  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
28  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: fixup.c,v 1.13 2022/01/01 01:15:11 macallan Exp $");
39 
40 #ifdef _KERNEL_OPT
41 #include "opt_ppcarch.h"
42 #endif
43 
44 #include <sys/param.h>
45 #include <sys/types.h>
46 
47 #include <powerpc/instr.h>
48 #include <powerpc/spr.h>
49 #include <powerpc/include/cpu.h>
50 #include <powerpc/include/oea/spr.h>
51 
52 static inline void
fixup_jump(uint32_t * insnp,const struct powerpc_jump_fixup_info * jfi)53 fixup_jump(uint32_t *insnp, const struct powerpc_jump_fixup_info *jfi)
54 {
55           union instr instr = { .i_int = *insnp };
56 
57           KASSERT(instr.i_any.i_opcd == OPC_B);
58 
59           instr.i_i.i_li = jfi->jfi_real - fixup_addr2offset(insnp);
60 
61           *insnp = instr.i_int;
62 
63           __asm(
64                     "dcbst    0,%0"     "\n\t"
65                     "sync"              "\n\t"
66                     "icbi     0,%0"     "\n\t"
67                     "sync"              "\n\t"
68                     "isync"
69               :: "b"(insnp));
70 }
71 
72 void
powerpc_fixup_stubs(uint32_t * start,uint32_t * end,uint32_t * stub_start,uint32_t * stub_end)73 powerpc_fixup_stubs(uint32_t *start, uint32_t *end,
74           uint32_t *stub_start, uint32_t *stub_end)
75 {
76           extern uint32_t __stub_start[], __stub_end[];
77 #ifdef DEBUG
78           size_t fixups_done = 0;
79           uint64_t cycles = 0;
80 #ifdef PPC_OEA601
81           if ((mfpvr() >> 16) == MPC601)
82               cycles = rtc_nanosecs() >> 7;
83           else
84 #endif
85               cycles = mftb();
86 #endif
87 
88           if (stub_start == NULL) {
89                     stub_start = __stub_start;
90                     stub_end = __stub_end;
91           }
92 
93           if (end > __stub_start)
94                     end = __stub_start;
95 
96           for (uint32_t *insnp = start; insnp < end; insnp++) {
97                     struct powerpc_jump_fixup_info fixup;
98                     union instr instr = { .i_int = *insnp };
99                     uint32_t *stub = insnp + instr.i_i.i_li;
100                     u_int opcode = instr.i_any.i_opcd;
101 
102                     /*
103                      * First we check to see if this is a jump and whether it is
104                      * within the range we are interested in.
105                      */
106                     if (opcode != OPC_B || stub < stub_start || stub_end <= stub)
107                               continue;
108 
109                     fixup.jfi_stub = fixup_addr2offset(stub);
110                     fixup.jfi_real = 0;
111 
112                     /*
113                      * We know it's a jump, now we need to figure out where it goes.
114                      */
115                     register_t fixreg[32];
116                     register_t ctr = 0;
117                     uint32_t valid_mask = (1 << 1);
118 #ifdef DIAGNOSTIC
119                     int r_lr = -1;
120 #endif
121                     for (; stub < stub_end && fixup.jfi_real == 0; stub++) {
122                               const union instr i = { .i_int = *stub };
123 
124                               switch (i.i_any.i_opcd) {
125                               case OPC_integer_31: {
126                                         const u_int rs = i.i_x.i_rs;
127                                         const u_int ra = i.i_x.i_ra;
128                                         const u_int rb = i.i_x.i_rb;
129                                         switch (i.i_x.i_xo) {
130                                         case OPC31_MFSPR: {
131 #ifdef DIAGNOSTIC
132                                                   const u_int spr = (rb << 5) | ra;
133                                                   KASSERT(spr == SPR_LR);
134                                                   r_lr = rs;
135 #endif
136                                                   valid_mask |= (1 << rs);
137                                                   break;
138                                         }
139                                         case OPC31_MTSPR: {
140 #ifdef DIAGNOSTIC
141                                                   const u_int spr = (rb << 5) | ra;
142                                                   KASSERT(valid_mask & (1 << rs));
143                                                   KASSERT(spr == SPR_CTR);
144 #endif
145                                                   ctr = fixreg[rs];
146                                                   break;
147                                         }
148                                         case OPC31_OR: {
149 #ifdef DIAGNOSTIC
150                                                   KASSERT(valid_mask & (1 << rs));
151                                                   KASSERT(valid_mask & (1 << rb));
152 #endif
153                                                   fixreg[ra] = fixreg[rs] | fixreg[rb];
154                                                   valid_mask |= 1 << ra;
155                                                   break;
156                                         }
157                                         default:
158                                                   panic("%s: %p: unexpected insn 0x%08x",
159                                                       __func__, stub, i.i_int);
160                                         }
161                                         break;
162                               }
163                               case OPC_ADDI:
164                               case OPC_ADDIS: {
165                                         const u_int rs = i.i_d.i_rs;
166                                         const u_int ra = i.i_d.i_ra;
167                                         register_t d = i.i_d.i_d << ((i.i_d.i_opcd & 1) * 16);
168                                         if (ra) {
169                                                   KASSERT(valid_mask & (1 << ra));
170                                                   d += fixreg[ra];
171                                         }
172                                         fixreg[rs] = d;
173                                         valid_mask |= (1 << rs);
174                                         break;
175                               }
176                               case OPC_LWZ: {
177                                         const u_int rs = i.i_d.i_rs;
178                                         const u_int ra = i.i_d.i_ra;
179                                         register_t addr = i.i_d.i_d;
180                                         if (ra) {
181                                                   KASSERT(valid_mask & (1 << ra));
182                                                   addr += fixreg[ra];
183                                         }
184                                         fixreg[rs] = *(uint32_t *)addr;
185                                         valid_mask |= (1 << rs);
186                                         break;
187                               }
188                               case OPC_STW: {
189 #ifdef DIAGNOSTIC
190                                         KASSERT((i.i_d.i_rs == r_lr || i.i_d.i_rs == 31) && i.i_d.i_ra == 1);
191 #endif
192                                         break;
193                               }
194                               case OPC_STWU: {
195                                         KASSERT(i.i_d.i_rs == 1 && i.i_d.i_ra == 1);
196                                         KASSERT(i.i_d.i_d < 0);
197                                         break;
198                               }
199                               case OPC_branch_19: {
200 #ifdef DIAGNOSTIC
201                                         KASSERT(r_lr == -1 || i.i_int == 0x4e800421);
202                                         KASSERT(r_lr != -1 || i.i_int == 0x4e800420);
203 #endif
204                                         if (ctr == 0) {
205                                                   panic("%s: jump at %p to %p would "
206                                                       "branch to 0", __func__, insnp,
207                                                       insnp + instr.i_i.i_li);
208                                         }
209                                         fixup.jfi_real = fixup_addr2offset(ctr);
210                                         break;
211                               }
212                               case OPC_RLWINM: {
213                                         // LLVM emits these for bool operands.
214                                         // Ignore them.
215                                         break;
216                               }
217                               default: {
218                                         panic("%s: %p: unexpected insn 0x%08x",
219                                                       __func__, stub, i.i_int);
220                               }
221                               }
222                     }
223                     KASSERT(fixup.jfi_real != 0);
224                     /*
225                      * Now we know the real destination to branch to.  Replace the
226                      * old displacement with the new displacement.
227                      */
228 #if 0
229                     printf("%s: %p: change from %#x to %#x\n",
230                         __func__, insnp, fixup.jfi_stub << 2, fixup.jfi_real << 2);
231 #endif
232                     fixup_jump(insnp, &fixup);
233 #ifdef DEBUG
234                     fixups_done++;
235 #endif
236           }
237 
238 #ifdef DEBUG
239 
240 #ifdef PPC_OEA601
241           if ((mfpvr() >> 16) == MPC601)
242               cycles = (rtc_nanosecs() >> 7) - cycles;
243           else
244 #endif
245           cycles = mftb() - cycles;
246 
247           printf("%s: %zu fixup%s done in %"PRIu64" cycles\n", __func__,
248               fixups_done, fixups_done == 1 ? "" : "s",
249               cycles);
250 #endif
251 }
252 
253 void
cpu_fixup_stubs(void)254 cpu_fixup_stubs(void)
255 {
256           extern uint32_t _ftext[];
257           extern uint32_t _etext[];
258 
259           powerpc_fixup_stubs(_ftext, _etext, NULL, NULL);
260 }
261