If hw supports it, on atomic operation that requires exclusive ownership of more than one cache line, #AC is generated.
Thread can control it with sysarch(I386_SET_SPLITLOCK). The global default is set with hw.splitlock_force.
Details
Diff Detail
- Repository
- rG FreeBSD src repository
- Lint
Lint Skipped - Unit
Tests Skipped
Event Timeline
Test program I used with simics:
/* $Id: splitlock.c,v 1.5 2026/09/18 16:39:53 kostik Exp kostik $ */
#include <sys/param.h>
#include <sys/mman.h>
#include <machine/atomic.h>
#include <machine/sysarch.h>
#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef I386_SET_SPLITLOCK
#define I386_SET_SPLITLOCK 14
#define I386_GET_SPLITLOCK 15
#endif
static void
sigbus_handler(int signo, siginfo_t *si, void *mc)
{
printf("sig%d si_code %d addr %p\n", signo, si->si_code, si->si_addr);
exit(0);
}
int
main(void)
{
char *m;
struct sigaction sa;
int error, val;
val = 1;
error = sysarch(I386_SET_SPLITLOCK, &val);
if (error == -1)
err(1, "I386_SET_SPLITLOCK");
error = sysarch(I386_GET_SPLITLOCK, &val);
if (error == -1)
err(1, "I386_GET_SPLITLOCK");
printf("setlock: %d\n", val);
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigbus_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGBUS, &sa, NULL) == -1)
err(1, "sigaction");
m = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (m == MAP_FAILED)
err(1, "mmap");
m += 256 - 1;
atomic_fetchadd_long((unsigned long *)m, 1);
}Fix splitlock control on exec for non-native ABIs.
Cache MSR_MEMORY_CTL, avoiding msr read on the context switch.
Use non-seializing immediate form for WRMSR when available.
If I understand correctly, #AC isn't raised if a splitlock operation happens while in kernel mode. But, what about places that use casuword() and friends? They will behave the same as before even if splitlock detection is enabled.
Isn't casuword() executed in kernel mode?
Hm, I also had an impression that #AC only happen when CPL is 3. But re-checking SDM 11.1.2.3 Features to Disable Bus Locks and description of the #AC exception, I started doubt it.
I think it costs nothing to add the recovery for #AC handler in kernel mode.
Add kernel-mode #AC handler.
Copy md_td_flags from the right source.
Use atomic_load() in context switch code.
I checked, simics does generates #AC in CPL 0, the current patch correctly returns EFAULT from umtx_op UMTX_OP_MUTEX_LOCK on mis-aligned struct umutex.
I wonder if it should be a procctl feature instead of sysarch()? If your intent is to set ia32_splitlock_force = 1 by default, then it would be nice if proccontrol could be used to override application settings without having to modify the program or change the global default. Hmm, but I guess this wouldn't work if the policy is to reset the splitlock setting upon exec.
| sys/amd64/amd64/exec_machdep.c | ||
|---|---|---|
| 415 | What if !ia32_splitlock is true? disable_splitlock() asserts MPASS(ia32_splitlock). | |
| sys/amd64/amd64/pmap.c | ||
| 10256 | I think the oldtd != td check is redundant: if oldtd == td then the oldpmap == pmap case above should be taken. | |
My first version of the patch controlled it with MD procctl. But I felt it was too 'tweaky' for such minor and probably not too popular feature. After all, we do not provide a procctl knob to enable %rflags.AC, and nobody asked for that.
If somebody wants to enable either alignment checks or disable splitlocks, this can be done with a trivial LD_PRELOADed dso. The static binaries are off (like Go-compiled binaries) but I do not feel guilty.
In exec_splitlock(), do not call disable_splitlock() if hw does not support it.
Remove oldtd != td check in context switch.