Page MenuHomeFreeBSD

amd64: implement support for disabling splitlocks
Needs ReviewPublic

Authored by kib on Fri, Sep 18, 4:53 PM.
Tags
None
Referenced Files
F172883394: D59815.diff
Mon, Sep 21, 8:09 PM
F172883392: D59815.diff
Mon, Sep 21, 8:09 PM
F172787121: D59815.diff
Mon, Sep 21, 12:05 AM
F172786701: D59815.diff
Mon, Sep 21, 12:00 AM
Unknown Object (File)
Sun, Sep 20, 3:08 PM
Unknown Object (File)
Sat, Sep 19, 9:17 AM
Unknown Object (File)
Sat, Sep 19, 12:46 AM
Unknown Object (File)
Fri, Sep 18, 11:39 PM
Subscribers

Details

Reviewers
markj
jhb
Summary

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.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

kib requested review of this revision.Fri, Sep 18, 4:53 PM

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 reversed handling of the msr bit on context switch.

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.

sys/amd64/amd64/pmap.c
10257

This should load flags with atomic_load_*.

sys/amd64/amd64/sys_machdep.c
457

Extra break.

sys/amd64/amd64/vm_machdep.c
185

Shouldn't it be loading td1->td_md.md_td_flags?

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.

kib marked 3 inline comments as done.Sun, Sep 20, 4:41 PM

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.

In D59815#1373713, @kib wrote:

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.

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.

kib marked 2 inline comments as done.Mon, Sep 21, 5:16 AM

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.

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.

sys/amd64/amd64/pmap.c
10256

Hmm, but this is a per-thread setting, not per-process. So in the oldpmap == pmap case, where we are switching between two threads in the same proc, the per-thread splitlock setting might not be applied correctly.

sys/amd64/include/proc.h
72

It should be (k) I believe.

kib marked 2 inline comments as done.

Fix handling of context switches.