mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
6.8 KiB
285 lines
6.8 KiB
/* |
|
* bpf_jib_asm.S: Packet/header access helper functions for MIPS/MIPS64 BPF |
|
* compiler. |
|
* |
|
* Copyright (C) 2015 Imagination Technologies Ltd. |
|
* Author: Markos Chandras <markos.chandras@imgtec.com> |
|
* |
|
* 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 of the License. |
|
*/ |
|
|
|
#include <asm/asm.h> |
|
#include <asm/isa-rev.h> |
|
#include <asm/regdef.h> |
|
#include "bpf_jit.h" |
|
|
|
/* ABI |
|
* |
|
* r_skb_hl skb header length |
|
* r_skb_data skb data |
|
* r_off(a1) offset register |
|
* r_A BPF register A |
|
* r_X PF register X |
|
* r_skb(a0) *skb |
|
* r_M *scratch memory |
|
* r_skb_le skb length |
|
* r_s0 Scratch register 0 |
|
* r_s1 Scratch register 1 |
|
* |
|
* On entry: |
|
* a0: *skb |
|
* a1: offset (imm or imm + X) |
|
* |
|
* All non-BPF-ABI registers are free for use. On return, we only |
|
* care about r_ret. The BPF-ABI registers are assumed to remain |
|
* unmodified during the entire filter operation. |
|
*/ |
|
|
|
#define skb a0 |
|
#define offset a1 |
|
#define SKF_LL_OFF (-0x200000) /* Can't include linux/filter.h in assembly */ |
|
|
|
/* We know better :) so prevent assembler reordering etc */ |
|
.set noreorder |
|
|
|
#define is_offset_negative(TYPE) \ |
|
/* If offset is negative we have more work to do */ \ |
|
slti t0, offset, 0; \ |
|
bgtz t0, bpf_slow_path_##TYPE##_neg; \ |
|
/* Be careful what follows in DS. */ |
|
|
|
#define is_offset_in_header(SIZE, TYPE) \ |
|
/* Reading from header? */ \ |
|
addiu $r_s0, $r_skb_hl, -SIZE; \ |
|
slt t0, $r_s0, offset; \ |
|
bgtz t0, bpf_slow_path_##TYPE; \ |
|
|
|
LEAF(sk_load_word) |
|
is_offset_negative(word) |
|
FEXPORT(sk_load_word_positive) |
|
is_offset_in_header(4, word) |
|
/* Offset within header boundaries */ |
|
PTR_ADDU t1, $r_skb_data, offset |
|
.set reorder |
|
lw $r_A, 0(t1) |
|
.set noreorder |
|
#ifdef CONFIG_CPU_LITTLE_ENDIAN |
|
# if MIPS_ISA_REV >= 2 |
|
wsbh t0, $r_A |
|
rotr $r_A, t0, 16 |
|
# else |
|
sll t0, $r_A, 24 |
|
srl t1, $r_A, 24 |
|
srl t2, $r_A, 8 |
|
or t0, t0, t1 |
|
andi t2, t2, 0xff00 |
|
andi t1, $r_A, 0xff00 |
|
or t0, t0, t2 |
|
sll t1, t1, 8 |
|
or $r_A, t0, t1 |
|
# endif |
|
#endif |
|
jr $r_ra |
|
move $r_ret, zero |
|
END(sk_load_word) |
|
|
|
LEAF(sk_load_half) |
|
is_offset_negative(half) |
|
FEXPORT(sk_load_half_positive) |
|
is_offset_in_header(2, half) |
|
/* Offset within header boundaries */ |
|
PTR_ADDU t1, $r_skb_data, offset |
|
lhu $r_A, 0(t1) |
|
#ifdef CONFIG_CPU_LITTLE_ENDIAN |
|
# if MIPS_ISA_REV >= 2 |
|
wsbh $r_A, $r_A |
|
# else |
|
sll t0, $r_A, 8 |
|
srl t1, $r_A, 8 |
|
andi t0, t0, 0xff00 |
|
or $r_A, t0, t1 |
|
# endif |
|
#endif |
|
jr $r_ra |
|
move $r_ret, zero |
|
END(sk_load_half) |
|
|
|
LEAF(sk_load_byte) |
|
is_offset_negative(byte) |
|
FEXPORT(sk_load_byte_positive) |
|
is_offset_in_header(1, byte) |
|
/* Offset within header boundaries */ |
|
PTR_ADDU t1, $r_skb_data, offset |
|
lbu $r_A, 0(t1) |
|
jr $r_ra |
|
move $r_ret, zero |
|
END(sk_load_byte) |
|
|
|
/* |
|
* call skb_copy_bits: |
|
* (prototype in linux/skbuff.h) |
|
* |
|
* int skb_copy_bits(sk_buff *skb, int offset, void *to, int len) |
|
* |
|
* o32 mandates we leave 4 spaces for argument registers in case |
|
* the callee needs to use them. Even though we don't care about |
|
* the argument registers ourselves, we need to allocate that space |
|
* to remain ABI compliant since the callee may want to use that space. |
|
* We also allocate 2 more spaces for $r_ra and our return register (*to). |
|
* |
|
* n64 is a bit different. The *caller* will allocate the space to preserve |
|
* the arguments. So in 64-bit kernels, we allocate the 4-arg space for no |
|
* good reason but it does not matter that much really. |
|
* |
|
* (void *to) is returned in r_s0 |
|
* |
|
*/ |
|
#ifdef CONFIG_CPU_LITTLE_ENDIAN |
|
#define DS_OFFSET(SIZE) (4 * SZREG) |
|
#else |
|
#define DS_OFFSET(SIZE) ((4 * SZREG) + (4 - SIZE)) |
|
#endif |
|
#define bpf_slow_path_common(SIZE) \ |
|
/* Quick check. Are we within reasonable boundaries? */ \ |
|
LONG_ADDIU $r_s1, $r_skb_len, -SIZE; \ |
|
sltu $r_s0, offset, $r_s1; \ |
|
beqz $r_s0, fault; \ |
|
/* Load 4th argument in DS */ \ |
|
LONG_ADDIU a3, zero, SIZE; \ |
|
PTR_ADDIU $r_sp, $r_sp, -(6 * SZREG); \ |
|
PTR_LA t0, skb_copy_bits; \ |
|
PTR_S $r_ra, (5 * SZREG)($r_sp); \ |
|
/* Assign low slot to a2 */ \ |
|
PTR_ADDIU a2, $r_sp, DS_OFFSET(SIZE); \ |
|
jalr t0; \ |
|
/* Reset our destination slot (DS but it's ok) */ \ |
|
INT_S zero, (4 * SZREG)($r_sp); \ |
|
/* \ |
|
* skb_copy_bits returns 0 on success and -EFAULT \ |
|
* on error. Our data live in a2. Do not bother with \ |
|
* our data if an error has been returned. \ |
|
*/ \ |
|
/* Restore our frame */ \ |
|
PTR_L $r_ra, (5 * SZREG)($r_sp); \ |
|
INT_L $r_s0, (4 * SZREG)($r_sp); \ |
|
bltz v0, fault; \ |
|
PTR_ADDIU $r_sp, $r_sp, 6 * SZREG; \ |
|
move $r_ret, zero; \ |
|
|
|
NESTED(bpf_slow_path_word, (6 * SZREG), $r_sp) |
|
bpf_slow_path_common(4) |
|
#ifdef CONFIG_CPU_LITTLE_ENDIAN |
|
# if MIPS_ISA_REV >= 2 |
|
wsbh t0, $r_s0 |
|
jr $r_ra |
|
rotr $r_A, t0, 16 |
|
# else |
|
sll t0, $r_s0, 24 |
|
srl t1, $r_s0, 24 |
|
srl t2, $r_s0, 8 |
|
or t0, t0, t1 |
|
andi t2, t2, 0xff00 |
|
andi t1, $r_s0, 0xff00 |
|
or t0, t0, t2 |
|
sll t1, t1, 8 |
|
jr $r_ra |
|
or $r_A, t0, t1 |
|
# endif |
|
#else |
|
jr $r_ra |
|
move $r_A, $r_s0 |
|
#endif |
|
|
|
END(bpf_slow_path_word) |
|
|
|
NESTED(bpf_slow_path_half, (6 * SZREG), $r_sp) |
|
bpf_slow_path_common(2) |
|
#ifdef CONFIG_CPU_LITTLE_ENDIAN |
|
# if MIPS_ISA_REV >= 2 |
|
jr $r_ra |
|
wsbh $r_A, $r_s0 |
|
# else |
|
sll t0, $r_s0, 8 |
|
andi t1, $r_s0, 0xff00 |
|
andi t0, t0, 0xff00 |
|
srl t1, t1, 8 |
|
jr $r_ra |
|
or $r_A, t0, t1 |
|
# endif |
|
#else |
|
jr $r_ra |
|
move $r_A, $r_s0 |
|
#endif |
|
|
|
END(bpf_slow_path_half) |
|
|
|
NESTED(bpf_slow_path_byte, (6 * SZREG), $r_sp) |
|
bpf_slow_path_common(1) |
|
jr $r_ra |
|
move $r_A, $r_s0 |
|
|
|
END(bpf_slow_path_byte) |
|
|
|
/* |
|
* Negative entry points |
|
*/ |
|
.macro bpf_is_end_of_data |
|
li t0, SKF_LL_OFF |
|
/* Reading link layer data? */ |
|
slt t1, offset, t0 |
|
bgtz t1, fault |
|
/* Be careful what follows in DS. */ |
|
.endm |
|
/* |
|
* call skb_copy_bits: |
|
* (prototype in linux/filter.h) |
|
* |
|
* void *bpf_internal_load_pointer_neg_helper(const struct sk_buff *skb, |
|
* int k, unsigned int size) |
|
* |
|
* see above (bpf_slow_path_common) for ABI restrictions |
|
*/ |
|
#define bpf_negative_common(SIZE) \ |
|
PTR_ADDIU $r_sp, $r_sp, -(6 * SZREG); \ |
|
PTR_LA t0, bpf_internal_load_pointer_neg_helper; \ |
|
PTR_S $r_ra, (5 * SZREG)($r_sp); \ |
|
jalr t0; \ |
|
li a2, SIZE; \ |
|
PTR_L $r_ra, (5 * SZREG)($r_sp); \ |
|
/* Check return pointer */ \ |
|
beqz v0, fault; \ |
|
PTR_ADDIU $r_sp, $r_sp, 6 * SZREG; \ |
|
/* Preserve our pointer */ \ |
|
move $r_s0, v0; \ |
|
/* Set return value */ \ |
|
move $r_ret, zero; \ |
|
|
|
bpf_slow_path_word_neg: |
|
bpf_is_end_of_data |
|
NESTED(sk_load_word_negative, (6 * SZREG), $r_sp) |
|
bpf_negative_common(4) |
|
jr $r_ra |
|
lw $r_A, 0($r_s0) |
|
END(sk_load_word_negative) |
|
|
|
bpf_slow_path_half_neg: |
|
bpf_is_end_of_data |
|
NESTED(sk_load_half_negative, (6 * SZREG), $r_sp) |
|
bpf_negative_common(2) |
|
jr $r_ra |
|
lhu $r_A, 0($r_s0) |
|
END(sk_load_half_negative) |
|
|
|
bpf_slow_path_byte_neg: |
|
bpf_is_end_of_data |
|
NESTED(sk_load_byte_negative, (6 * SZREG), $r_sp) |
|
bpf_negative_common(1) |
|
jr $r_ra |
|
lbu $r_A, 0($r_s0) |
|
END(sk_load_byte_negative) |
|
|
|
fault: |
|
jr $r_ra |
|
addiu $r_ret, zero, 1
|
|
|