]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
powerpc/bpf: enforce full ordering for ATOMIC operations with BPF_FETCH
authorPuranjay Mohan <puranjay@kernel.org>
Mon, 13 May 2024 10:02:48 +0000 (10:02 +0000)
committerMichael Ellerman <mpe@ellerman.id.au>
Wed, 29 May 2024 12:12:42 +0000 (22:12 +1000)
The Linux Kernel Memory Model [1][2] requires RMW operations that have a
return value to be fully ordered.

BPF atomic operations with BPF_FETCH (including BPF_XCHG and
BPF_CMPXCHG) return a value back so they need to be JITed to fully
ordered operations. POWERPC currently emits relaxed operations for
these.

We can show this by running the following litmus-test:

  PPC SB+atomic_add+fetch

  {
      0:r0=x;  (* dst reg assuming offset is 0 *)
      0:r1=2;  (* src reg *)
      0:r2=1;
      0:r4=y;  (* P0 writes to this, P1 reads this *)
      0:r5=z;  (* P1 writes to this, P0 reads this *)
      0:r6=0;

      1:r2=1;
      1:r4=y;
      1:r5=z;
  }

  P0                      | P1            ;
  stw         r2, 0(r4)   | stw  r2,0(r5) ;
                          |               ;
  loop:lwarx  r3, r6, r0  |               ;
  mr          r8, r3      |               ;
  add         r3, r3, r1  | sync          ;
  stwcx.      r3, r6, r0  |               ;
  bne         loop        |               ;
  mr          r1, r8      |               ;
                          |               ;
  lwa         r7, 0(r5)   | lwa  r7,0(r4) ;

  ~exists(0:r7=0 /\ 1:r7=0)

  Witnesses
  Positive: 9 Negative: 3
  Condition ~exists (0:r7=0 /\ 1:r7=0)
  Observation SB+atomic_add+fetch Sometimes 3 9

This test shows that the older store in P0 is reordered with a newer
load to a different address. Although there is a RMW operation with
fetch between them. Adding a sync before and after RMW fixes the issue:

  Witnesses
  Positive: 9 Negative: 0
  Condition ~exists (0:r7=0 /\ 1:r7=0)
  Observation SB+atomic_add+fetch Never 0 9

[1] https://www.kernel.org/doc/Documentation/memory-barriers.txt
[2] https://www.kernel.org/doc/Documentation/atomic_t.txt

Fixes: aea7ef8a82c0 ("powerpc/bpf/32: add support for BPF_ATOMIC bitwise operations")
Fixes: 2d9206b22743 ("powerpc/bpf/32: Add instructions for atomic_[cmp]xchg")
Fixes: dbe6e2456fb0 ("powerpc/bpf/64: add support for atomic fetch operations")
Fixes: 1e82dfaa7819 ("powerpc/bpf/64: Add instructions for atomic_[cmp]xchg")
Cc: stable@vger.kernel.org # v6.0+
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Reviewed-by: Christophe Leroy <christophe.leroy@csgroup.eu>
Reviewed-by: Naveen N Rao <naveen@kernel.org>
Acked-by: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20240513100248.110535-1-puranjay@kernel.org
arch/powerpc/net/bpf_jit_comp32.c
arch/powerpc/net/bpf_jit_comp64.c

index 43b97032a91c00a5b9ed8cc56c603c3c71dbc999..a0c4f1bde83e86da7524d56aa8a3408a6cacc647 100644 (file)
@@ -900,6 +900,15 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code
 
                        /* Get offset into TMP_REG */
                        EMIT(PPC_RAW_LI(tmp_reg, off));
+                       /*
+                        * Enforce full ordering for operations with BPF_FETCH by emitting a 'sync'
+                        * before and after the operation.
+                        *
+                        * This is a requirement in the Linux Kernel Memory Model.
+                        * See __cmpxchg_u32() in asm/cmpxchg.h as an example.
+                        */
+                       if ((imm & BPF_FETCH) && IS_ENABLED(CONFIG_SMP))
+                               EMIT(PPC_RAW_SYNC());
                        tmp_idx = ctx->idx * 4;
                        /* load value from memory into r0 */
                        EMIT(PPC_RAW_LWARX(_R0, tmp_reg, dst_reg, 0));
@@ -953,6 +962,9 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code
 
                        /* For the BPF_FETCH variant, get old data into src_reg */
                        if (imm & BPF_FETCH) {
+                               /* Emit 'sync' to enforce full ordering */
+                               if (IS_ENABLED(CONFIG_SMP))
+                                       EMIT(PPC_RAW_SYNC());
                                EMIT(PPC_RAW_MR(ret_reg, ax_reg));
                                if (!fp->aux->verifier_zext)
                                        EMIT(PPC_RAW_LI(ret_reg - 1, 0)); /* higher 32-bit */
index 8afc14a4a1258ebab01c802efdaa1c55cb73fd1b..7703dcf48be86bfd82f08f264a1735509b12cbff 100644 (file)
@@ -846,6 +846,15 @@ emit_clear:
 
                        /* Get offset into TMP_REG_1 */
                        EMIT(PPC_RAW_LI(tmp1_reg, off));
+                       /*
+                        * Enforce full ordering for operations with BPF_FETCH by emitting a 'sync'
+                        * before and after the operation.
+                        *
+                        * This is a requirement in the Linux Kernel Memory Model.
+                        * See __cmpxchg_u64() in asm/cmpxchg.h as an example.
+                        */
+                       if ((imm & BPF_FETCH) && IS_ENABLED(CONFIG_SMP))
+                               EMIT(PPC_RAW_SYNC());
                        tmp_idx = ctx->idx * 4;
                        /* load value from memory into TMP_REG_2 */
                        if (size == BPF_DW)
@@ -908,6 +917,9 @@ emit_clear:
                        PPC_BCC_SHORT(COND_NE, tmp_idx);
 
                        if (imm & BPF_FETCH) {
+                               /* Emit 'sync' to enforce full ordering */
+                               if (IS_ENABLED(CONFIG_SMP))
+                                       EMIT(PPC_RAW_SYNC());
                                EMIT(PPC_RAW_MR(ret_reg, _R0));
                                /*
                                 * Skip unnecessary zero-extension for 32-bit cmpxchg.