Skip to content

Commit 5ca419f

Browse files
bjackmanAlexei Starovoitov
authored andcommitted
bpf: Add BPF_FETCH field / create atomic_fetch_add instruction
The BPF_FETCH field can be set in bpf_insn.imm, for BPF_ATOMIC instructions, in order to have the previous value of the atomically-modified memory location loaded into the src register after an atomic op is carried out. Suggested-by: Yonghong Song <yhs@fb.com> Signed-off-by: Brendan Jackman <jackmanb@google.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Acked-by: John Fastabend <john.fastabend@gmail.com> Link: https://lore.kernel.org/bpf/20210114181751.768687-7-jackmanb@google.com
1 parent c5bcb5e commit 5ca419f

File tree

8 files changed

+56
-9
lines changed

8 files changed

+56
-9
lines changed

arch/x86/net/bpf_jit_comp.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,10 @@ static int emit_atomic(u8 **pprog, u8 atomic_op,
811811
/* lock *(u32/u64*)(dst_reg + off) <op>= src_reg */
812812
EMIT1(simple_alu_opcodes[atomic_op]);
813813
break;
814+
case BPF_ADD | BPF_FETCH:
815+
/* src_reg = atomic_fetch_add(dst_reg + off, src_reg); */
816+
EMIT2(0x0F, 0xC1);
817+
break;
814818
default:
815819
pr_err("bpf_jit: unknown atomic opcode %02x\n", atomic_op);
816820
return -EFAULT;

include/linux/filter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ static inline bool insn_is_zext(const struct bpf_insn *insn)
264264
* Atomic operations:
265265
*
266266
* BPF_ADD *(uint *) (dst_reg + off16) += src_reg
267+
* BPF_ADD | BPF_FETCH src_reg = atomic_fetch_add(dst_reg + off16, src_reg);
267268
*/
268269

269270
#define BPF_ATOMIC_OP(SIZE, OP, DST, SRC, OFF) \

include/uapi/linux/bpf.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
#define BPF_CALL 0x80 /* function call */
4545
#define BPF_EXIT 0x90 /* function return */
4646

47+
/* atomic op type fields (stored in immediate) */
48+
#define BPF_FETCH 0x01 /* fetch previous value into src reg */
49+
4750
/* Register numbers */
4851
enum {
4952
BPF_REG_0 = 0,

kernel/bpf/core.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,16 +1624,29 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn, u64 *stack)
16241624
/* lock xadd *(u32 *)(dst_reg + off16) += src_reg */
16251625
atomic_add((u32) SRC, (atomic_t *)(unsigned long)
16261626
(DST + insn->off));
1627+
break;
1628+
case BPF_ADD | BPF_FETCH:
1629+
SRC = (u32) atomic_fetch_add(
1630+
(u32) SRC,
1631+
(atomic_t *)(unsigned long) (DST + insn->off));
1632+
break;
16271633
default:
16281634
goto default_label;
16291635
}
16301636
CONT;
1637+
16311638
STX_ATOMIC_DW:
16321639
switch (IMM) {
16331640
case BPF_ADD:
16341641
/* lock xadd *(u64 *)(dst_reg + off16) += src_reg */
16351642
atomic64_add((u64) SRC, (atomic64_t *)(unsigned long)
16361643
(DST + insn->off));
1644+
break;
1645+
case BPF_ADD | BPF_FETCH:
1646+
SRC = (u64) atomic64_fetch_add(
1647+
(u64) SRC,
1648+
(atomic64_t *)(unsigned long) (DST + insn->off));
1649+
break;
16371650
default:
16381651
goto default_label;
16391652
}

kernel/bpf/disasm.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
160160
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
161161
insn->dst_reg, insn->off,
162162
insn->src_reg);
163+
} else if (BPF_MODE(insn->code) == BPF_ATOMIC &&
164+
insn->imm == (BPF_ADD | BPF_FETCH)) {
165+
verbose(cbs->private_data, "(%02x) r%d = atomic%s_fetch_add((%s *)(r%d %+d), r%d)\n",
166+
insn->code, insn->src_reg,
167+
BPF_SIZE(insn->code) == BPF_DW ? "64" : "",
168+
bpf_ldst_string[BPF_SIZE(insn->code) >> 3],
169+
insn->dst_reg, insn->off, insn->src_reg);
163170
} else {
164171
verbose(cbs->private_data, "BUG_%02x\n", insn->code);
165172
}

kernel/bpf/verifier.c

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3608,7 +3608,11 @@ static int check_atomic(struct bpf_verifier_env *env, int insn_idx, struct bpf_i
36083608
{
36093609
int err;
36103610

3611-
if (insn->imm != BPF_ADD) {
3611+
switch (insn->imm) {
3612+
case BPF_ADD:
3613+
case BPF_ADD | BPF_FETCH:
3614+
break;
3615+
default:
36123616
verbose(env, "BPF_ATOMIC uses invalid atomic opcode %02x\n", insn->imm);
36133617
return -EINVAL;
36143618
}
@@ -3650,8 +3654,20 @@ static int check_atomic(struct bpf_verifier_env *env, int insn_idx, struct bpf_i
36503654
return err;
36513655

36523656
/* check whether we can write into the same memory */
3653-
return check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
3654-
BPF_SIZE(insn->code), BPF_WRITE, -1, true);
3657+
err = check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
3658+
BPF_SIZE(insn->code), BPF_WRITE, -1, true);
3659+
if (err)
3660+
return err;
3661+
3662+
if (!(insn->imm & BPF_FETCH))
3663+
return 0;
3664+
3665+
/* check and record load of old value into src reg */
3666+
err = check_reg_arg(env, insn->src_reg, DST_OP);
3667+
if (err)
3668+
return err;
3669+
3670+
return 0;
36553671
}
36563672

36573673
static int __check_stack_boundary(struct bpf_verifier_env *env, u32 regno,
@@ -9528,12 +9544,6 @@ static int do_check(struct bpf_verifier_env *env)
95289544
} else if (class == BPF_STX) {
95299545
enum bpf_reg_type *prev_dst_type, dst_reg_type;
95309546

9531-
if (((BPF_MODE(insn->code) != BPF_MEM &&
9532-
BPF_MODE(insn->code) != BPF_ATOMIC) || insn->imm != 0)) {
9533-
verbose(env, "BPF_STX uses reserved fields\n");
9534-
return -EINVAL;
9535-
}
9536-
95379547
if (BPF_MODE(insn->code) == BPF_ATOMIC) {
95389548
err = check_atomic(env, env->insn_idx, insn);
95399549
if (err)
@@ -9542,6 +9552,11 @@ static int do_check(struct bpf_verifier_env *env)
95429552
continue;
95439553
}
95449554

9555+
if (BPF_MODE(insn->code) != BPF_MEM || insn->imm != 0) {
9556+
verbose(env, "BPF_STX uses reserved fields\n");
9557+
return -EINVAL;
9558+
}
9559+
95459560
/* check src1 operand */
95469561
err = check_reg_arg(env, insn->src_reg, SRC_OP);
95479562
if (err)

tools/include/linux/filter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
* Atomic operations:
174174
*
175175
* BPF_ADD *(uint *) (dst_reg + off16) += src_reg
176+
* BPF_ADD | BPF_FETCH src_reg = atomic_fetch_add(dst_reg + off16, src_reg);
176177
*/
177178

178179
#define BPF_ATOMIC_OP(SIZE, OP, DST, SRC, OFF) \

tools/include/uapi/linux/bpf.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
#define BPF_CALL 0x80 /* function call */
4545
#define BPF_EXIT 0x90 /* function return */
4646

47+
/* atomic op type fields (stored in immediate) */
48+
#define BPF_FETCH 0x01 /* fetch previous value into src reg */
49+
4750
/* Register numbers */
4851
enum {
4952
BPF_REG_0 = 0,

0 commit comments

Comments
 (0)