// SPDX-License-Identifier: GPL-2.0
/*
* BPF JIT compiler for PA-RISC (32-bit)
*
* Copyright (c) 2023 Helge Deller <deller@gmx.de>
*
* The code is based on the BPF JIT compiler for RV64 by Björn Töpel and
* the BPF JIT compiler for 32-bit ARM by Shubham Bansal and Mircea Gherzan.
*/
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/libgcc.h>
#include "bpf_jit.h"
/*
* Stack layout during BPF program execution (note: stack grows up):
*
* high
* HPPA32 sp => +----------+ <= HPPA32 fp
* | saved sp |
* | saved rp |
* | ... | HPPA32 callee-saved registers
* | curr args|
* | local var|
* +----------+ <= (sp - 4 * NR_SAVED_REGISTERS)
* | lo(R9) |
* | hi(R9) |
* | lo(FP) | JIT scratch space for BPF registers
* | hi(FP) |
* | ... |
* +----------+ <= (sp - 4 * NR_SAVED_REGISTERS
* | | - 4 * BPF_JIT_SCRATCH_REGS)
* | |
* | ... | BPF program stack
* | |
* | ... | Function call stack
* | |
* +----------+
* low
*/
enum {
/* Stack layout - these are offsets from top of JIT scratch space. */
BPF_R8_HI,
BPF_R8_LO,
BPF_R9_HI,
BPF_R9_LO,
BPF_FP_HI,
BPF_FP_LO,
BPF_AX_HI,
BPF_AX_LO,
BPF_R0_TEMP_HI,
BPF_R0_TEMP_LO,
BPF_JIT_SCRATCH_REGS,
};
/* Number of callee-saved registers stored to stack: rp, r3-r18. */
#define NR_SAVED_REGISTERS (18 - 3 + 1 + 8)
/* Offset from fp for BPF registers stored on stack. */
#define STACK_OFFSET(k) (- (NR_SAVED_REGISTERS + k + 1))
#define STACK_ALIGN FRAME_SIZE
#define EXIT_PTR_LOAD(reg) hppa_ldw(-0x08, HPPA_REG_SP, reg)
#define EXIT_PTR_STORE(reg) hppa_stw(reg, -0x08, HPPA_REG_SP)
#define EXIT_PTR_JUMP(reg, nop) hppa_bv(HPPA_REG_ZERO, reg, nop)
#define TMP_REG_1 (MAX_BPF_JIT_REG + 0)
#define TMP_REG_2 (MAX_BPF_JIT_REG + 1)
#define TMP_REG_R0 (MAX_BPF_JIT_REG + 2)
static const s8 regmap[][2] = {
/* Return value from in-kernel function, and exit value from eBPF. */
[BPF_REG_0] = {HPPA_REG_RET0, HPPA_REG_RET1}, /* HI/LOW */
/* Arguments from eBPF program to in-kernel function. */
[BPF_REG_1] = {HPPA_R(3), HPPA_R(4)},
[BPF_REG_2] = {HPPA_R(5), HPPA_R(6)},
[BPF_REG_3] = {HPPA_R(7), HPPA_R(8)},
[BPF_REG_4] = {HPPA_R(9), HPPA_R(10)},
[BPF_REG_5] = {HPPA_R(11), HPPA_R(12)},
[BPF_REG_6] = {HPPA_R(13), HPPA_R(14)},
[BPF_REG_7] = {HPPA_R(15), HPPA_R(16)},
/*
* Callee-saved registers that in-kernel function will preserve.
* Stored on the stack.
*/
[BPF_REG_8] = {STACK_OFFSET(BPF_R8_HI), STACK_OFFSET(BPF_R8_LO)},
[BPF_REG_9] = {STACK_OFFSET(BPF_R9_HI), STACK_OFFSET(BPF_R9_LO)},
/* Read-only frame pointer to access BPF stack. Not needed. */
[BPF_REG_FP] = {STACK_OFFSET(BPF_FP_HI), STACK_OFFSET(BPF_FP_LO)},
/* Temporary register for blinding constants. Stored on the stack. */
[BPF_REG_AX] = {STACK_OFFSET(BPF_AX_HI), STACK_OFFSET(BPF_AX_LO)},
/*
* Temporary registers used by the JIT to operate on registers stored
* on the stack. Save t0 and t1 to be used as temporaries in generated
* code.
*/
[TMP_REG_1] = {HPPA_REG_T3, HPPA_REG_T2},
[TMP_REG_2] = {HPPA_REG_T5, HPPA_REG_T4},
/* temporary space for BPF_R0 during libgcc and millicode calls */
[TMP_REG_R0] = {STACK_OFFSET(BPF_R0_TEMP_HI), STACK_OFFSET(BPF_R0_TEMP_LO)},
};
static s8 hi(const s8 *r)
{
return r[0];
}
static s8 lo(const s8 *r)
{
return r[1];
}
static void emit_hppa_copy(const s8 rs, const s8 rd, struct hppa_jit_context *ctx)
{
REG_SET_SEEN(ctx, rd);
if (OPTIMIZE_HPPA && (rs == rd))
return;
REG_SET_SEEN(ctx, rs);
emit(hppa_copy(rs, rd), ctx);
}
static void emit_hppa_xor(const s8 r1, const s8 r2, const s8 r3, struct hppa_jit_context *ctx)
{
REG_SET_SEEN(ctx, r1);
REG_SET_SEEN(ctx, r2);
REG_SET_SEEN(ctx, r3);
if (OPTIMIZE_HPPA && (r1 == r2)) {
emit(hppa_copy(HPPA_REG_ZERO, r3), ctx);
} else {
emit(hppa_xor(r1, r2, r3), ctx);
}
}
static void emit_imm(const s8 rd, s32 imm, struct hppa_jit_context *ctx)
{
u32 lower = im11(imm);
REG_SET_SEEN(ctx, rd);
if (OPTIMIZE_HPPA && relative_bits_ok(imm, 14)) {
emit(hppa_ldi(imm, rd), ctx);
return;
}
emit(hppa_ldil(imm, rd), ctx);
if (OPTIMIZE_HPPA && (lower == 0))
return;
emit(hppa_ldo(lower, rd, rd), ctx);
}
static void emit_imm32(const s8 *rd, s32 imm, struct hppa_jit_context *ctx)
{
/* Emit immediate into lower bits. */
REG_SET_SEEN(ctx, lo(rd));
emi
|