|
| 1 | +/* |
| 2 | + * RISC-V RV64 (LP64D ABI) stack switching implementation |
| 3 | + * |
| 4 | + * This file provides stack manipulation for RISC-V 64-bit (RV64) using the |
| 5 | + * LP64D ABI (64-bit pointers, hardware double-precision floating point). |
| 6 | + * |
| 7 | + * RISC-V RV64 LP64D Calling Convention: |
| 8 | + * - Arguments: a0-a7 (x10-x17) |
| 9 | + * - Return: a0-a1 (x10-x11) |
| 10 | + * - Callee-saved: s0-s11 (x8-x9, x18-x27), sp (x2) |
| 11 | + * - Caller-saved: a0-a7, t0-t6, ra (x1) |
| 12 | + * - Stack pointer: sp (x2) - must be 16-byte aligned |
| 13 | + * - Frame pointer: s0/fp (x8) - optional |
| 14 | + */ |
| 15 | + |
| 16 | +/* ELF metadata and CFI directives */ |
| 17 | +#define FUNCTION(name) .globl name ; .type name, @function |
| 18 | +#define LABEL(name) name: |
| 19 | +#define TYPE_FUNCTION(name) .type name, @function |
| 20 | +#define SIZE_FUNCTION(name) .size name, .-name |
| 21 | +#define GNU_STACK .section .note.GNU-stack,"",@progbits |
| 22 | +#define CFI_STARTPROC .cfi_startproc |
| 23 | +#define CFI_ENDPROC .cfi_endproc |
| 24 | +#define CFI_DEF_CFA_OFFSET(x) .cfi_def_cfa_offset x |
| 25 | +#define CFI_OFFSET(r,o) .cfi_offset r, o |
| 26 | +#define CFI_RESTORE(r) .cfi_restore r |
| 27 | + |
| 28 | + .file "switch_riscv64_gcc.S" |
| 29 | + .text |
| 30 | + .align 1 |
| 31 | + |
| 32 | +/* |
| 33 | + * void *stackman_switch(stackman_cb_t callback, void *context) |
| 34 | + * |
| 35 | + * Saves current stack state, calls callback twice with stack pointer, |
| 36 | + * and switches to new stack returned by first callback invocation. |
| 37 | + * |
| 38 | + * Register usage: |
| 39 | + * - a0: callback function pointer (first arg) |
| 40 | + * - a1: context pointer (second arg) |
| 41 | + * - Callee-saved regs: s0-s11 must be preserved |
| 42 | + * - FP regs: fs0-fs11 (callee-saved floating point) |
| 43 | + */ |
| 44 | + FUNCTION(stackman_switch) |
| 45 | +LABEL(stackman_switch) |
| 46 | +.LFB0: |
| 47 | + CFI_STARTPROC |
| 48 | + |
| 49 | + /* Allocate stack frame (160 bytes) |
| 50 | + * Layout: ra(8) + s0-s11(12*8=96) + fs0-fs11(12*8=96) = 200 bytes |
| 51 | + * Round to 208 for 16-byte alignment |
| 52 | + */ |
| 53 | + addi sp, sp, -208 |
| 54 | + CFI_DEF_CFA_OFFSET(208) |
| 55 | + |
| 56 | + /* Save return address and callee-saved registers */ |
| 57 | + sd ra, 200(sp) |
| 58 | + CFI_OFFSET(1, -8) /* ra is x1 */ |
| 59 | + sd s0, 192(sp) |
| 60 | + CFI_OFFSET(8, -16) |
| 61 | + sd s1, 184(sp) |
| 62 | + CFI_OFFSET(9, -24) |
| 63 | + sd s2, 176(sp) |
| 64 | + CFI_OFFSET(18, -32) |
| 65 | + sd s3, 168(sp) |
| 66 | + CFI_OFFSET(19, -40) |
| 67 | + sd s4, 160(sp) |
| 68 | + CFI_OFFSET(20, -48) |
| 69 | + sd s5, 152(sp) |
| 70 | + CFI_OFFSET(21, -56) |
| 71 | + sd s6, 144(sp) |
| 72 | + CFI_OFFSET(22, -64) |
| 73 | + sd s7, 136(sp) |
| 74 | + CFI_OFFSET(23, -72) |
| 75 | + sd s8, 128(sp) |
| 76 | + CFI_OFFSET(24, -80) |
| 77 | + sd s9, 120(sp) |
| 78 | + CFI_OFFSET(25, -88) |
| 79 | + sd s10, 112(sp) |
| 80 | + CFI_OFFSET(26, -96) |
| 81 | + sd s11, 104(sp) |
| 82 | + CFI_OFFSET(27, -104) |
| 83 | + |
| 84 | + /* Save callee-saved floating point registers */ |
| 85 | + fsd fs0, 96(sp) |
| 86 | + fsd fs1, 88(sp) |
| 87 | + fsd fs2, 80(sp) |
| 88 | + fsd fs3, 72(sp) |
| 89 | + fsd fs4, 64(sp) |
| 90 | + fsd fs5, 56(sp) |
| 91 | + fsd fs6, 48(sp) |
| 92 | + fsd fs7, 40(sp) |
| 93 | + fsd fs8, 32(sp) |
| 94 | + fsd fs9, 24(sp) |
| 95 | + fsd fs10, 16(sp) |
| 96 | + fsd fs11, 8(sp) |
| 97 | + |
| 98 | + /* Preserve callback and context in callee-saved registers */ |
| 99 | + mv s2, a0 /* s2 = callback */ |
| 100 | + mv s3, a1 /* s3 = context */ |
| 101 | + |
| 102 | + /* First callback: callback(context, opcode, sp) */ |
| 103 | + mv a0, s3 /* a0 = context */ |
| 104 | + li a1, 0 /* a1 = STACKMAN_OP_SAVE */ |
| 105 | + mv a2, sp /* a2 = current sp */ |
| 106 | + jalr s2 /* call callback */ |
| 107 | + |
| 108 | + /* Save returned pointer and switch stack */ |
| 109 | + mv s4, a0 /* s4 = returned value */ |
| 110 | + mv sp, a0 /* switch to new stack */ |
| 111 | + |
| 112 | + /* Second callback: callback(context, opcode, sp) */ |
| 113 | + mv a0, s3 /* a0 = context */ |
| 114 | + li a1, 1 /* a1 = STACKMAN_OP_RESTORE */ |
| 115 | + mv a2, sp /* a2 = new sp */ |
| 116 | + jalr s2 /* call callback */ |
| 117 | + |
| 118 | + /* Restore callee-saved floating point registers */ |
| 119 | + fld fs11, 8(sp) |
| 120 | + fld fs10, 16(sp) |
| 121 | + fld fs9, 24(sp) |
| 122 | + fld fs8, 32(sp) |
| 123 | + fld fs7, 40(sp) |
| 124 | + fld fs6, 48(sp) |
| 125 | + fld fs5, 56(sp) |
| 126 | + fld fs4, 64(sp) |
| 127 | + fld fs3, 72(sp) |
| 128 | + fld fs2, 80(sp) |
| 129 | + fld fs1, 88(sp) |
| 130 | + fld fs0, 96(sp) |
| 131 | + |
| 132 | + /* Restore callee-saved integer registers */ |
| 133 | + ld s11, 104(sp) |
| 134 | + CFI_RESTORE(27) |
| 135 | + ld s10, 112(sp) |
| 136 | + CFI_RESTORE(26) |
| 137 | + ld s9, 120(sp) |
| 138 | + CFI_RESTORE(25) |
| 139 | + ld s8, 128(sp) |
| 140 | + CFI_RESTORE(24) |
| 141 | + ld s7, 136(sp) |
| 142 | + CFI_RESTORE(23) |
| 143 | + ld s6, 144(sp) |
| 144 | + CFI_RESTORE(22) |
| 145 | + ld s5, 152(sp) |
| 146 | + CFI_RESTORE(21) |
| 147 | + ld s4, 160(sp) |
| 148 | + CFI_RESTORE(20) |
| 149 | + ld s3, 168(sp) |
| 150 | + CFI_RESTORE(19) |
| 151 | + ld s2, 176(sp) |
| 152 | + CFI_RESTORE(18) |
| 153 | + ld s1, 184(sp) |
| 154 | + CFI_RESTORE(9) |
| 155 | + ld s0, 192(sp) |
| 156 | + CFI_RESTORE(8) |
| 157 | + ld ra, 200(sp) |
| 158 | + CFI_RESTORE(1) |
| 159 | + |
| 160 | + /* Deallocate frame and return */ |
| 161 | + addi sp, sp, 208 |
| 162 | + CFI_DEF_CFA_OFFSET(0) |
| 163 | + ret |
| 164 | + |
| 165 | + CFI_ENDPROC |
| 166 | +.LFE0: |
| 167 | + SIZE_FUNCTION(stackman_switch) |
| 168 | + |
| 169 | +/* |
| 170 | + * void *stackman_call(stackman_cb_t callback, void *context, void *new_stack) |
| 171 | + * |
| 172 | + * Similar to stackman_switch but uses explicitly provided stack pointer. |
| 173 | + * |
| 174 | + * Register usage: |
| 175 | + * - a0: callback function pointer |
| 176 | + * - a1: context pointer |
| 177 | + * - a2: new stack pointer |
| 178 | + */ |
| 179 | + .align 1 |
| 180 | + FUNCTION(stackman_call) |
| 181 | +LABEL(stackman_call) |
| 182 | +.LFB1: |
| 183 | + CFI_STARTPROC |
| 184 | + |
| 185 | + /* Allocate frame for ra and s0 (callee-saved) */ |
| 186 | + addi sp, sp, -16 |
| 187 | + CFI_DEF_CFA_OFFSET(16) |
| 188 | + |
| 189 | + /* Save return address and s0 (we'll use it to preserve old sp) */ |
| 190 | + sd ra, 8(sp) |
| 191 | + CFI_OFFSET(1, -8) |
| 192 | + sd s0, 0(sp) |
| 193 | + CFI_OFFSET(8, -16) |
| 194 | + |
| 195 | + /* Save old stack pointer in s0 (callee-saved register) */ |
| 196 | + mv s0, sp |
| 197 | + |
| 198 | + /* Setup args for callback and switch to new stack */ |
| 199 | + mv t0, a0 /* t0 = callback */ |
| 200 | + mv t1, a2 /* t1 = new_stack (for arg) */ |
| 201 | + mv sp, a2 /* switch to new stack */ |
| 202 | + |
| 203 | + /* Call callback(context, opcode, sp) */ |
| 204 | + mv a0, a1 /* a0 = context */ |
| 205 | + li a1, 2 /* a1 = STACKMAN_OP_CALL */ |
| 206 | + mv a2, t1 /* a2 = new_stack */ |
| 207 | + jalr t0 /* call callback */ |
| 208 | + |
| 209 | + /* Restore old stack pointer from s0 */ |
| 210 | + mv sp, s0 |
| 211 | + |
| 212 | + /* Restore s0 and return address */ |
| 213 | + ld s0, 0(sp) |
| 214 | + CFI_RESTORE(8) |
| 215 | + ld ra, 8(sp) |
| 216 | + CFI_RESTORE(1) |
| 217 | + |
| 218 | + addi sp, sp, 16 |
| 219 | + CFI_DEF_CFA_OFFSET(0) |
| 220 | + ret |
| 221 | + |
| 222 | + CFI_ENDPROC |
| 223 | +.LFE1: |
| 224 | + SIZE_FUNCTION(stackman_call) |
| 225 | + |
| 226 | +/* Mark stack as non-executable */ |
| 227 | +GNU_STACK |
0 commit comments