Skip to content

Commit fa5a46c

Browse files
ouptonigaw
authored andcommitted
common.h: Avoid using unsupported load/store instructions in arm64 VMs
Using nvme show-regs within a VM on arm64 can sometimes lead to VM termination. To answer why this happens: one of the deficiencies of the Arm architecture is that there exists a range of load/store instructions that have insufficient decode information for traps taken to the hypervisor. KVM, for example, may raise an external abort or outright terminate the VM depending on the configuration. This is a known problem on the kernel side, and is fixed by using assembly MMIO accessors w/ 'safe' load/store instructions. So do exactly that, providing arm64-specific accessors and falling back to plain old volatile pointer accesses for other architectures. Reported-by: William Butler <wab@google.com> Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
1 parent 9afc400 commit fa5a46c

File tree

1 file changed

+50
-12
lines changed

1 file changed

+50
-12
lines changed

common.h

+50-12
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,81 @@
1717
#define __packed __attribute__((__packed__))
1818
#endif /* __packed */
1919

20-
static inline uint32_t mmio_read32(void *addr)
20+
/*
21+
* VMs on arm64 can only use a subset of instructions for MMIO that provide
22+
* the hypervisor with a complete instruction decode. Provide assembly MMIO
23+
* accessors to prevent the compiler from using a possibly unsupported
24+
* instruction.
25+
*
26+
* See kernel commit c726200dd106 ("KVM: arm/arm64: Allow reporting non-ISV
27+
* data aborts to userspace") for more details.
28+
*/
29+
#if defined(__aarch64__)
30+
static inline leint32_t __raw_readl(const volatile leint32_t *addr)
31+
{
32+
leint32_t val;
33+
34+
asm volatile("ldr %w0, %1" : "=r" (val) : "Qo" (*addr));
35+
36+
return val;
37+
}
38+
39+
static inline void __raw_writel(volatile leint32_t *addr, leint32_t val)
2140
{
22-
leint32_t *p = addr;
41+
asm volatile("str %w0, %1" : : "r" (val), "Qo" (*addr));
42+
}
2343

24-
return le32_to_cpu(*p);
44+
static inline void __raw_writeq(volatile leint64_t *addr, leint64_t val)
45+
{
46+
asm volatile("str %0, %1" : : "r" (val), "Qo" (*addr));
47+
}
48+
#else
49+
static inline leint32_t __raw_readl(volatile leint32_t *addr)
50+
{
51+
return *addr;
52+
}
53+
54+
static inline void __raw_writel(volatile leint32_t *addr, leint32_t val)
55+
{
56+
*addr = val;
57+
}
58+
59+
static inline void __raw_writeq(volatile leint64_t *addr, leint64_t val)
60+
{
61+
*addr = val;
62+
}
63+
#endif
64+
65+
static inline uint32_t mmio_read32(void *addr)
66+
{
67+
return le32_to_cpu(__raw_readl(addr));
2568
}
2669

2770
/* Access 64-bit registers as 2 32-bit; Some devices fail 64-bit MMIO. */
2871
static inline uint64_t mmio_read64(void *addr)
2972
{
30-
const volatile uint32_t *p = addr;
3173
uint32_t low, high;
3274

33-
low = le32_to_cpu(*p);
34-
high = le32_to_cpu(*(p + 1));
75+
low = le32_to_cpu(__raw_readl(addr));
76+
high = le32_to_cpu(__raw_readl(addr + sizeof(leint32_t)));
3577

3678
return ((uint64_t)high << 32) | low;
3779
}
3880

3981
static inline void mmio_write32(void *addr, uint32_t value)
4082
{
41-
leint32_t *p = addr;
42-
43-
*p = cpu_to_le32(value);
83+
__raw_writel(addr, cpu_to_le32(value));
4484
}
4585

4686
/* Access 64-bit registers as 2 32-bit if write32 flag set; Some devices fail 64-bit MMIO. */
4787
static inline void mmio_write64(void *addr, uint64_t value, bool write32)
4888
{
49-
uint64_t *p = addr;
50-
5189
if (write32) {
5290
mmio_write32(addr, value);
5391
mmio_write32((uint32_t *)addr + 1, value >> 32);
5492
return;
5593
}
5694

57-
*p = cpu_to_le64(value);
95+
__raw_writeq(addr, cpu_to_le64(value));
5896
}
5997
#endif

0 commit comments

Comments
 (0)