mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-04 04:04:19 +00:00
LKMM updates for v6.3
Documentation updates. Add read-modify-write sequences, which means that stronger primitives more consistently result in stronger ordering, while still remaining in the envelope of the hardware that supports Linux. Address, data, and control dependencies used to ignore data that was stored in temporaries. This update extends these dependency chains to include unmarked intra-thread stores and loads. Note that these unmarked stores and loads should not be concurrently accessed from multiple threads, and doing so will cause LKMM to flag such accesses as data races. -----BEGIN PGP SIGNATURE----- iQJHBAABCgAxFiEEbK7UrM+RBIrCoViJnr8S83LZ+4wFAmPtYNQTHHBhdWxtY2tA a2VybmVsLm9yZwAKCRCevxLzctn7jHMaEACfruLJV/hao7R1et0CLasWL6LMenq6 MbzFiOWDMBXxTMUOdAEeM5JQiIsHr8XZbs80hX1OEQb9VPG61HMy/8jqTYtfbGGt 3EykqAKQ8my1/7wEPSfrO/icvPf/czuT1GYYNQi+PGnlrBKUHPkqfuDpPz5E6p/+ hIojbtXcFLIdB42sBw5JSG3itX6lTlmJFZEfmkYCIlgBQxGTlbK7Bpagml+7zGTD mQT824OKiPJ6aerUuBzCUURq45JvNd9jE39Gc5KV63pxR2hOcsCZz3jYA1ZQcKeX UP+ZowKC3WH6iLhxmhnsdAIlaeQRcOvU46B0PHdwIKhV1CVLZR4qINPFIPJ2u4Oo kwsdG8hBHnNnapPMnhmk8DOZRz1SX2Q9ZR35ZOtOOFWw41ZRkGp3fE6JlKaF0pEt 3SlZ98wkxpV5YEQ67clpVGCPdg0yMWNnos4D1Yw82mpI2DH5NF60R5x6Gb/B2QyX fp/0SpkXwc4PbLY7sHYWH1MF+bRFkJOeDw2XesMMT+Cjn20fqtR0HGFO/rPHeDqQ qqamNFQVkP7Y/BWzxR27iH9xFqI8a8BKI18/IYbfQZ+eNwULOCCXqdQpuRaTKPaM 4h6Ebtx/j3oXR0TYtb84mWwaKNO17fo8zMH4tn1Jk+K4OrcxCop5m29fkX1Fjqqf BMpxir7tN4DK7Q== =uGmQ -----END PGP SIGNATURE----- Merge tag 'lkmm.2023.02.15a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu Pull LKMM (Linux Kernel Memory Model) updates from Paul McKenney: "Documentation updates. Add read-modify-write sequences, which means that stronger primitives more consistently result in stronger ordering, while still remaining in the envelope of the hardware that supports Linux. Address, data, and control dependencies used to ignore data that was stored in temporaries. This update extends these dependency chains to include unmarked intra-thread stores and loads. Note that these unmarked stores and loads should not be concurrently accessed from multiple threads, and doing so will cause LKMM to flag such accesses as data races" * tag 'lkmm.2023.02.15a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu: tools: memory-model: Make plain accesses carry dependencies Documentation: Fixed a typo in atomic_t.txt tools: memory-model: Add rmw-sequences to the LKMM locking/memory-barriers.txt: Improve documentation for writel() example
This commit is contained in:
commit
192a5e0a19
@ -324,7 +324,7 @@ atomic operations.
|
||||
|
||||
Specifically 'simple' cmpxchg() loops are expected to not starve one another
|
||||
indefinitely. However, this is not evident on LL/SC architectures, because
|
||||
while an LL/SC architecure 'can/should/must' provide forward progress
|
||||
while an LL/SC architecture 'can/should/must' provide forward progress
|
||||
guarantees between competing LL/SC sections, such a guarantee does not
|
||||
transfer to cmpxchg() implemented using LL/SC. Consider:
|
||||
|
||||
|
@ -1910,7 +1910,8 @@ There are some more advanced barrier functions:
|
||||
|
||||
These are for use with consistent memory to guarantee the ordering
|
||||
of writes or reads of shared memory accessible to both the CPU and a
|
||||
DMA capable device.
|
||||
DMA capable device. See Documentation/core-api/dma-api.rst file for more
|
||||
information about consistent memory.
|
||||
|
||||
For example, consider a device driver that shares memory with a device
|
||||
and uses a descriptor status value to indicate if the descriptor belongs
|
||||
@ -1931,22 +1932,21 @@ There are some more advanced barrier functions:
|
||||
/* assign ownership */
|
||||
desc->status = DEVICE_OWN;
|
||||
|
||||
/* notify device of new descriptors */
|
||||
/* Make descriptor status visible to the device followed by
|
||||
* notify device of new descriptor
|
||||
*/
|
||||
writel(DESC_NOTIFY, doorbell);
|
||||
}
|
||||
|
||||
The dma_rmb() allows us guarantee the device has released ownership
|
||||
The dma_rmb() allows us to guarantee that the device has released ownership
|
||||
before we read the data from the descriptor, and the dma_wmb() allows
|
||||
us to guarantee the data is written to the descriptor before the device
|
||||
can see it now has ownership. The dma_mb() implies both a dma_rmb() and
|
||||
a dma_wmb(). Note that, when using writel(), a prior wmb() is not needed
|
||||
to guarantee that the cache coherent memory writes have completed before
|
||||
writing to the MMIO region. The cheaper writel_relaxed() does not provide
|
||||
this guarantee and must not be used here.
|
||||
a dma_wmb().
|
||||
|
||||
See the subsection "Kernel I/O barrier effects" for more information on
|
||||
relaxed I/O accessors and the Documentation/core-api/dma-api.rst file for
|
||||
more information on consistent memory.
|
||||
Note that the dma_*() barriers do not provide any ordering guarantees for
|
||||
accesses to MMIO regions. See the later "KERNEL I/O BARRIER EFFECTS"
|
||||
subsection for more information about I/O accessors and MMIO ordering.
|
||||
|
||||
(*) pmem_wmb();
|
||||
|
||||
|
@ -1007,6 +1007,36 @@ order. Equivalently,
|
||||
where the rmw relation links the read and write events making up each
|
||||
atomic update. This is what the LKMM's "atomic" axiom says.
|
||||
|
||||
Atomic rmw updates play one more role in the LKMM: They can form "rmw
|
||||
sequences". An rmw sequence is simply a bunch of atomic updates where
|
||||
each update reads from the previous one. Written using events, it
|
||||
looks like this:
|
||||
|
||||
Z0 ->rf Y1 ->rmw Z1 ->rf ... ->rf Yn ->rmw Zn,
|
||||
|
||||
where Z0 is some store event and n can be any number (even 0, in the
|
||||
degenerate case). We write this relation as: Z0 ->rmw-sequence Zn.
|
||||
Note that this implies Z0 and Zn are stores to the same variable.
|
||||
|
||||
Rmw sequences have a special property in the LKMM: They can extend the
|
||||
cumul-fence relation. That is, if we have:
|
||||
|
||||
U ->cumul-fence X -> rmw-sequence Y
|
||||
|
||||
then also U ->cumul-fence Y. Thinking about this in terms of the
|
||||
operational model, U ->cumul-fence X says that the store U propagates
|
||||
to each CPU before the store X does. Then the fact that X and Y are
|
||||
linked by an rmw sequence means that U also propagates to each CPU
|
||||
before Y does. In an analogous way, rmw sequences can also extend
|
||||
the w-post-bounded relation defined below in the PLAIN ACCESSES AND
|
||||
DATA RACES section.
|
||||
|
||||
(The notion of rmw sequences in the LKMM is similar to, but not quite
|
||||
the same as, that of release sequences in the C11 memory model. They
|
||||
were added to the LKMM to fix an obscure bug; without them, atomic
|
||||
updates with full-barrier semantics did not always guarantee ordering
|
||||
at least as strong as atomic updates with release-barrier semantics.)
|
||||
|
||||
|
||||
THE PRESERVED PROGRAM ORDER RELATION: ppo
|
||||
-----------------------------------------
|
||||
@ -2545,7 +2575,7 @@ smp_store_release() -- which is basically how the Linux kernel treats
|
||||
them.
|
||||
|
||||
Although we said that plain accesses are not linked by the ppo
|
||||
relation, they do contribute to it indirectly. Namely, when there is
|
||||
relation, they do contribute to it indirectly. Firstly, when there is
|
||||
an address dependency from a marked load R to a plain store W,
|
||||
followed by smp_wmb() and then a marked store W', the LKMM creates a
|
||||
ppo link from R to W'. The reasoning behind this is perhaps a little
|
||||
@ -2554,6 +2584,13 @@ for this source code in which W' could execute before R. Just as with
|
||||
pre-bounding by address dependencies, it is possible for the compiler
|
||||
to undermine this relation if sufficient care is not taken.
|
||||
|
||||
Secondly, plain accesses can carry dependencies: If a data dependency
|
||||
links a marked load R to a store W, and the store is read by a load R'
|
||||
from the same thread, then the data loaded by R' depends on the data
|
||||
loaded originally by R. Thus, if R' is linked to any access X by a
|
||||
dependency, R is also linked to access X by the same dependency, even
|
||||
if W' or R' (or both!) are plain.
|
||||
|
||||
There are a few oddball fences which need special treatment:
|
||||
smp_mb__before_atomic(), smp_mb__after_atomic(), and
|
||||
smp_mb__after_spinlock(). The LKMM uses fence events with special
|
||||
|
@ -82,3 +82,9 @@ flag ~empty different-values(srcu-rscs) as srcu-bad-nesting
|
||||
let Marked = (~M) | IW | Once | Release | Acquire | domain(rmw) | range(rmw) |
|
||||
LKR | LKW | UL | LF | RL | RU
|
||||
let Plain = M \ Marked
|
||||
|
||||
(* Redefine dependencies to include those carried through plain accesses *)
|
||||
let carry-dep = (data ; rfi)*
|
||||
let addr = carry-dep ; addr
|
||||
let ctrl = carry-dep ; ctrl
|
||||
let data = carry-dep ; data
|
||||
|
@ -74,8 +74,9 @@ let ppo = to-r | to-w | fence | (po-unlock-lock-po & int)
|
||||
|
||||
(* Propagation: Ordering from release operations and strong fences. *)
|
||||
let A-cumul(r) = (rfe ; [Marked])? ; r
|
||||
let rmw-sequence = (rf ; rmw)*
|
||||
let cumul-fence = [Marked] ; (A-cumul(strong-fence | po-rel) | wmb |
|
||||
po-unlock-lock-po) ; [Marked]
|
||||
po-unlock-lock-po) ; [Marked] ; rmw-sequence
|
||||
let prop = [Marked] ; (overwrite & ext)? ; cumul-fence* ;
|
||||
[Marked] ; rfe? ; [Marked]
|
||||
|
||||
@ -174,7 +175,7 @@ let vis = cumul-fence* ; rfe? ; [Marked] ;
|
||||
let w-pre-bounded = [Marked] ; (addr | fence)?
|
||||
let r-pre-bounded = [Marked] ; (addr | nonrw-fence |
|
||||
([R4rmb] ; fencerel(Rmb) ; [~Noreturn]))?
|
||||
let w-post-bounded = fence? ; [Marked]
|
||||
let w-post-bounded = fence? ; [Marked] ; rmw-sequence
|
||||
let r-post-bounded = (nonrw-fence | ([~Noreturn] ; fencerel(Rmb) ; [R4rmb]))? ;
|
||||
[Marked]
|
||||
|
||||
|
31
tools/memory-model/litmus-tests/dep+plain.litmus
Normal file
31
tools/memory-model/litmus-tests/dep+plain.litmus
Normal file
@ -0,0 +1,31 @@
|
||||
C dep+plain
|
||||
|
||||
(*
|
||||
* Result: Never
|
||||
*
|
||||
* This litmus test demonstrates that in LKMM, plain accesses
|
||||
* carry dependencies much like accesses to registers:
|
||||
* The data stored to *z1 and *z2 by P0() originates from P0()'s
|
||||
* READ_ONCE(), and therefore using that data to compute the
|
||||
* conditional of P0()'s if-statement creates a control dependency
|
||||
* from that READ_ONCE() to P0()'s WRITE_ONCE().
|
||||
*)
|
||||
|
||||
{}
|
||||
|
||||
P0(int *x, int *y, int *z1, int *z2)
|
||||
{
|
||||
int a = READ_ONCE(*x);
|
||||
*z1 = a;
|
||||
*z2 = *z1;
|
||||
if (*z2 == 1)
|
||||
WRITE_ONCE(*y, 1);
|
||||
}
|
||||
|
||||
P1(int *x, int *y)
|
||||
{
|
||||
int r = smp_load_acquire(y);
|
||||
smp_store_release(x, r);
|
||||
}
|
||||
|
||||
exists (x=1 /\ y=1)
|
Loading…
Reference in New Issue
Block a user