git: 16db4c6fff45 - main - amd64: Add kexec support

From: Justin Hibbits <jhibbits_at_FreeBSD.org>
Date: Mon, 27 Oct 2025 14:34:39 UTC
The branch main has been updated by jhibbits:

URL: https://cgit.FreeBSD.org/src/commit/?id=16db4c6fff450d3dd802b6c4b23cae0a8af44976

commit 16db4c6fff450d3dd802b6c4b23cae0a8af44976
Author:     Justin Hibbits <jhibbits@FreeBSD.org>
AuthorDate: 2025-10-26 02:45:15 +0000
Commit:     Justin Hibbits <jhibbits@FreeBSD.org>
CommitDate: 2025-10-27 14:33:51 +0000

    amd64: Add kexec support
    
    The biggest difference between this and arm64 kexec is that we can't
    disable the MMU for amd64, we have to instead create a new "safe" page
    table that the trampoline and "child" kernel can use.  This requires a
    lot more work to create identity mappings, etc.
    
    Reviewed by:    kib
    Sponsored by:   Juniper Networks, Inc.
    Differential Revision:  https://reviews.freebsd.org/D51623
---
 sys/amd64/amd64/genassym.c      |  12 ++
 sys/amd64/amd64/kexec_support.c | 300 ++++++++++++++++++++++++++++++++++++++++
 sys/amd64/amd64/kexec_tramp.S   |  91 ++++++++++++
 sys/amd64/include/kexec.h       |  41 ++++++
 sys/conf/files.amd64            |   2 +
 sys/x86/include/intr_machdep.h  |   1 +
 sys/x86/x86/intr_machdep.c      |  20 +++
 7 files changed, 467 insertions(+)

diff --git a/sys/amd64/amd64/genassym.c b/sys/amd64/amd64/genassym.c
index eb1b746f5893..2716784ee871 100644
--- a/sys/amd64/amd64/genassym.c
+++ b/sys/amd64/amd64/genassym.c
@@ -57,6 +57,7 @@
 #include <vm/vm_param.h>
 #include <vm/pmap.h>
 #include <vm/vm_map.h>
+#include <sys/kexec.h>
 #include <sys/proc.h>
 #include <x86/apicreg.h>
 #include <machine/cpu.h>
@@ -65,6 +66,7 @@
 #include <machine/proc.h>
 #include <machine/segments.h>
 #include <machine/efi.h>
+#include <machine/kexec.h>
 
 ASSYM(P_VMSPACE, offsetof(struct proc, p_vmspace));
 ASSYM(VM_PMAP, offsetof(struct vmspace, vm_pmap));
@@ -295,3 +297,13 @@ ASSYM(EC_R13, offsetof(struct efirt_callinfo, ec_r13));
 ASSYM(EC_R14, offsetof(struct efirt_callinfo, ec_r14));
 ASSYM(EC_R15, offsetof(struct efirt_callinfo, ec_r15));
 ASSYM(EC_RFLAGS, offsetof(struct efirt_callinfo, ec_rflags));
+
+/* Kexec */
+ASSYM(KEXEC_ENTRY, offsetof(struct kexec_image, entry));
+ASSYM(KEXEC_SEGMENTS, offsetof(struct kexec_image, segments));
+ASSYM(KEXEC_SEGMENT_MAX, KEXEC_SEGMENT_MAX);
+ASSYM(KEXEC_IMAGE_SIZE, sizeof(struct kexec_image));
+ASSYM(KEXEC_STAGED_SEGMENT_SIZE, sizeof(struct kexec_segment_stage));
+ASSYM(KEXEC_SEGMENT_SIZE, offsetof(struct kexec_segment_stage, size));
+ASSYM(KEXEC_SEGMENT_MAP, offsetof(struct kexec_segment_stage, map_buf));
+ASSYM(KEXEC_SEGMENT_TARGET, offsetof(struct kexec_segment_stage, target));
diff --git a/sys/amd64/amd64/kexec_support.c b/sys/amd64/amd64/kexec_support.c
new file mode 100644
index 000000000000..8189a48e9ae9
--- /dev/null
+++ b/sys/amd64/amd64/kexec_support.c
@@ -0,0 +1,300 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/interrupt.h>
+#include <sys/kernel.h>
+#include <sys/kexec.h>
+#include <vm/vm.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_object.h>
+#include <vm/vm_phys.h>
+#include <vm/pmap.h>
+#include <vm/vm_page.h>
+#include <vm/vm_radix.h>
+
+#include <machine/intr_machdep.h>
+#include <machine/kexec.h>
+#include <machine/md_var.h>
+#include <machine/pmap.h>
+#include <x86/apicvar.h>
+
+/*
+ * Idea behind this:
+ *
+ * kexec_load_md():
+ * - Update boot page tables (identity map) to include all pages needed before
+ *   disabling MMU.
+ *
+ * kexec_reboot_md():
+ * - Copy pages into target(s)
+ * - Do "other stuff"
+ * - Does not return
+ */
+
+/*
+ * do_pte: Create PTE entries (4k pages). If false, create 2MB superpages.
+ * identity: This is for an identity map, treat `start` as a physical address.
+ * Only valid here if do_pte is false.
+ */
+static void
+kexec_generate_page_tables(pml4_entry_t *root, vm_offset_t start,
+    vm_size_t size, bool do_pte, bool identity, struct pctrie_iter *pages)
+{
+	vm_paddr_t mpa;
+	vm_offset_t pg;
+	vm_size_t stride = do_pte ? PAGE_SIZE : NBPDR;
+	vm_page_t m;
+	vm_pindex_t i, j, k, l;
+
+	pg = start & ~(stride - 1);
+	i = pmap_pml4e_index(pg);
+	j = pmap_pdpe_index(pg);
+	k = pmap_pde_index(pg);
+	l = pmap_pte_index(pg);
+	for (; pg < start + size; i++, j = 0, k = 0, l = 0) {
+		/*
+		 * Walk linearly, as above, but one fell swoop, one page at a
+		 * time.
+		 */
+		if (root[i] == 0) {
+			m = vm_radix_iter_next(pages);
+			mpa = VM_PAGE_TO_PHYS(m);
+			root[i] = mpa | PG_RW | PG_V;
+		}
+		pdp_entry_t *pdp =
+			(pdp_entry_t *)(PHYS_TO_DMAP(root[i] & PG_FRAME));
+		for (; j < NPDPEPG && pg < start + size; j++, k = 0, l = 0) {
+			if (pdp[j] == 0) {
+				m = vm_radix_iter_next(pages);
+				mpa = VM_PAGE_TO_PHYS(m);
+				pdp[j] = mpa | PG_RW | PG_V;
+			}
+			pd_entry_t *pde =
+			    (pd_entry_t *)(PHYS_TO_DMAP(pdp[j] & PG_FRAME));
+			for (; k < NPDEPG && pg < start + size; k++, l = 0) {
+				if (pde[k] == 0) {
+					if (!do_pte) {
+						pde[k] =
+						    (identity ? pg : pmap_kextract(pg)) |
+						    PG_RW | PG_PS | PG_V;
+						pg += NBPDR;
+						continue;
+					}
+					m = vm_radix_iter_next(pages);
+					mpa = VM_PAGE_TO_PHYS(m);
+					pde[k] = mpa | PG_V | PG_RW;
+				} else if ((pde[k] & PG_PS) != 0) {
+					pg += NBPDR;
+					continue;
+				}
+				/* Populate the PTEs. */
+				for (; l < NPTEPG && pg < start + size;
+				    l++, pg += PAGE_SIZE) {
+					pt_entry_t *pte =
+					    (pt_entry_t *)PHYS_TO_DMAP(pde[pmap_pde_index(pg)] & PG_FRAME);
+					pte[pmap_pte_index(pg)] =
+					    pmap_kextract(pg) | PG_RW | PG_V;
+				}
+			}
+		}
+	}
+}
+
+void
+kexec_reboot_md(struct kexec_image *image)
+{
+	void (*kexec_do_tramp)(void) = image->md_image;
+
+	intr_disable_all();
+	lapic_disable();
+	kexec_do_reboot_trampoline(VM_PAGE_TO_PHYS(image->first_md_page),
+	    kexec_do_tramp);
+
+	for (;;)
+		;
+}
+
+int
+kexec_load_md(struct kexec_image *image)
+{
+	struct pctrie_iter pct_iter;
+	pml4_entry_t *PT4;
+	pdp_entry_t *PDP_l;
+	pd_entry_t *PD_l0;
+	vm_offset_t va;
+	int i;
+
+	/*
+	 * Start building the page table.
+	 * First part of the page table is standard for all.
+	 */
+	vm_offset_t pa_pdp_l, pa_pd_l0, pa_pd_l1, pa_pd_l2, pa_pd_l3;
+	vm_page_t m;
+
+	if (la57)
+		return (EINVAL);
+
+	vm_radix_iter_init(&pct_iter, &image->map_obj->rtree);
+	/* Working in linear space in the mapped space, `va` is our tracker. */
+	m = vm_radix_iter_lookup(&pct_iter, image->first_md_page->pindex);
+	va = (vm_offset_t)image->map_addr + ptoa(m->pindex);
+	/* We'll find a place for these later */
+	PT4 = (void *)va;
+	va += PAGE_SIZE;
+	m = vm_radix_iter_next(&pct_iter);
+	pa_pdp_l = VM_PAGE_TO_PHYS(m);
+	PDP_l = (void *)va;
+	va += PAGE_SIZE;
+	m = vm_radix_iter_next(&pct_iter);
+	pa_pd_l0 = VM_PAGE_TO_PHYS(m);
+	PD_l0 = (void *)va;
+	va += PAGE_SIZE;
+	m = vm_radix_iter_next(&pct_iter);
+	pa_pd_l1 = VM_PAGE_TO_PHYS(m);
+	m = vm_radix_iter_next(&pct_iter);
+	pa_pd_l2 = VM_PAGE_TO_PHYS(m);
+	m = vm_radix_iter_next(&pct_iter);
+	pa_pd_l3 = VM_PAGE_TO_PHYS(m);
+	m = vm_radix_iter_next(&pct_iter);
+
+	/* 1:1 mapping of lower 4G */
+	PT4[0] = (pml4_entry_t)pa_pdp_l | PG_V | PG_RW;
+	PDP_l[0] = (pdp_entry_t)pa_pd_l0 | PG_V | PG_RW;
+	PDP_l[1] = (pdp_entry_t)pa_pd_l1 | PG_V | PG_RW;
+	PDP_l[2] = (pdp_entry_t)pa_pd_l2 | PG_V | PG_RW;
+	PDP_l[3] = (pdp_entry_t)pa_pd_l3 | PG_V | PG_RW;
+	for (i = 0; i < 4 * NPDEPG; i++) {	/* we overflow PD_l0 into _l1, etc */
+		PD_l0[i] = ((pd_entry_t)i << PDRSHIFT) | PG_V |
+		    PG_RW | PG_PS;
+	}
+
+	/* Map the target(s) in 2MB chunks. */
+	for (i = 0; i < KEXEC_SEGMENT_MAX; i++) {
+		struct kexec_segment_stage *s = &image->segments[i];
+
+		if (s->size == 0)
+			break;
+		kexec_generate_page_tables(PT4, s->target, s->size, false,
+		    true, &pct_iter);
+	}
+	/* Now create the source page tables */
+	kexec_generate_page_tables(PT4, image->map_addr, image->map_size, true,
+	    false, &pct_iter);
+	kexec_generate_page_tables(PT4,
+	    trunc_page((vm_offset_t)kexec_do_reboot_trampoline),
+	    PAGE_SIZE, true, false, &pct_iter);
+	KASSERT(m != NULL, ("kexec_load_md: Missing trampoline page!\n"));
+
+	/* MD control pages start at this next page. */
+	image->md_image = (void *)(image->map_addr + ptoa(m->pindex));
+	bcopy(kexec_do_reboot, image->md_image, kexec_do_reboot_size);
+
+	/* Save the image into the MD page(s) right after the trampoline */
+	bcopy(image, (void *)((vm_offset_t)image->md_image +
+	    (vm_offset_t)&kexec_saved_image - (vm_offset_t)&kexec_do_reboot),
+	    sizeof(*image));
+
+	return (0);
+}
+
+/*
+ * Required pages:
+ * - L4 (1) (root)
+ * - L3 (PDPE) - 2 (bottom 512GB, bottom 4 used, top range for kernel map)
+ * - L2 (PDP) - 5 (2MB superpage mappings, 1GB each, for bottom 4GB, top 1)
+ * - L1 (PDR) - 1 (kexec trampoline page, first MD page)
+ * - kexec_do_reboot trampoline - 1
+ * - Slop pages for staging (in case it's not aligned nicely) - 3 (worst case)
+ *
+ * Minimum 9 pages for the direct map.
+ */
+int
+kexec_md_pages(struct kexec_segment *seg_in)
+{
+	struct kexec_segment *segs = seg_in;
+	vm_size_t pages = 13;	/* Minimum number of starting pages */
+	vm_paddr_t cur_addr = (1UL << 32) - 1;	/* Bottom 4G will be identity mapped in full */
+	vm_size_t source_total = 0;
+
+	for (int i = 0; i < KEXEC_SEGMENT_MAX; i++) {
+		vm_offset_t start, end;
+		if (segs[i].memsz == 0)
+			break;
+
+		end = round_2mpage((vm_offset_t)segs[i].mem + segs[i].memsz);
+		start = trunc_2mpage((vm_offset_t)segs[i].mem);
+		start = max(start, cur_addr + 1);
+		/*
+		 * Round to cover the full range of page table pages for each
+		 * segment.
+		 */
+		source_total += round_2mpage(end - start);
+
+		/*
+		 * Bottom 4GB are identity mapped already in the count, so skip
+		 * any segments that end up there, this will short-circuit that.
+		 */
+		if (end <= cur_addr + 1)
+			continue;
+
+		if (pmap_pml4e_index(end) != pmap_pml4e_index(cur_addr)) {
+			/* Need a new 512GB mapping page */
+			pages++;
+			pages += howmany(end - (start & ~PML4MASK), NBPML4);
+			pages += howmany(end - (start & ~PDPMASK), NBPDP);
+			pages += howmany(end - (start & ~PDRMASK), NBPDR);
+
+		} else if (pmap_pdpe_index(end) != pmap_pdpe_index(cur_addr)) {
+			pages++;
+			pages += howmany(end - (start & ~PDPMASK), NBPDP) - 1;
+			pages += howmany(end - (start & ~PDRMASK), NBPDR);
+		}
+
+	}
+	/* Be pessimistic when totaling up source pages.  We likely
+	 * can't use superpages, so need to map each page individually.
+	 */
+	pages += howmany(source_total, NBPDR);
+	pages += howmany(source_total, NBPDP);
+	pages += howmany(source_total, NBPML4);
+
+	/*
+	 * Be intentionally sloppy adding in the extra page table pages. It's
+	 * better to go over than under.
+	 */
+	pages += howmany(pages * PAGE_SIZE, NBPDR);
+	pages += howmany(pages * PAGE_SIZE, NBPDP);
+	pages += howmany(pages * PAGE_SIZE, NBPML4);
+
+	/* Add in the trampoline pages */
+	pages += howmany(kexec_do_reboot_size, PAGE_SIZE);
+
+	return (pages);
+}
diff --git a/sys/amd64/amd64/kexec_tramp.S b/sys/amd64/amd64/kexec_tramp.S
new file mode 100644
index 000000000000..6a2de676bc35
--- /dev/null
+++ b/sys/amd64/amd64/kexec_tramp.S
@@ -0,0 +1,91 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <machine/asmacros.h>
+#include <machine/specialreg.h>
+#include "assym.inc"
+
+/*
+ * Take a pointer to the image, copy each segment, and jump to the trampoline.
+ *
+ * Assumptions:
+ * - image is in safe memory
+ * - We're already running out of the new "identity" map.
+ * - All registers are free game, so go nuts
+ * - Interrupts are disabled
+ * - All APs are disabled
+ */
+ENTRY(kexec_do_reboot)
+	/*
+		r9:	image pointer
+		r10:	segment pointer
+		r11:	segment counter
+	 */
+	leaq	kexec_stack(%rip), %rsp
+	/* Get the saved kexec_image. */
+	leaq	kexec_saved_image(%rip), %r9
+	leaq	KEXEC_SEGMENTS(%r9), %r10
+	movq	$KEXEC_SEGMENT_MAX, %r11
+copy_segment:
+	movq	KEXEC_SEGMENT_SIZE(%r10), %rcx
+	cmpq	$0, %rcx
+	je	done
+	shrq	$3, %rcx
+	movq	KEXEC_SEGMENT_TARGET(%r10), %rdi
+	movq	KEXEC_SEGMENT_MAP(%r10), %rsi
+	rep
+	movsq
+	addq	$KEXEC_STAGED_SEGMENT_SIZE, %r10
+	decq	%r11
+	jg	copy_segment
+
+done:
+	pushq	KEXEC_ENTRY(%r9)
+	ret
+fail:
+	jmp	fail
+END(kexec_do_reboot)
+ENTRY(kexec_do_reboot_trampoline)
+	/* Set new page table, clears most of TLB. */
+	movq	%rdi, %cr3
+
+	/* Now flush the rest of the TLB, including global pages. */
+	movq	%cr4, %rax
+	andq	$~CR4_PGE, %rax
+	movq	%rax, %cr4
+	jmp	*%rsi
+END(kexec_do_reboot_trampoline)
+CNAME(kexec_saved_image):
+	.globl	kexec_saved_image
+	.space	KEXEC_IMAGE_SIZE
+	.quad	0
+	/* We don't need more than quad, so just fill out the page. */
+	.p2align PAGE_SHIFT
+	kexec_stack:
+CNAME(kexec_do_reboot_size):
+	.globl	kexec_do_reboot_size
+	.quad . - kexec_do_reboot
diff --git a/sys/amd64/include/kexec.h b/sys/amd64/include/kexec.h
new file mode 100644
index 000000000000..70bc2991be3f
--- /dev/null
+++ b/sys/amd64/include/kexec.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _AMD64_KEXEC_H_
+#define _AMD64_KEXEC_H_
+
+struct kexec_segment;
+struct kexec_image;
+int		 kexec_md_pages(struct kexec_segment *);
+extern void	 kexec_do_reboot(void);
+extern long	 kexec_do_reboot_size;
+extern void	*kexec_saved_image;
+extern void	 kexec_do_reboot_trampoline(unsigned long, void (*)(void));
+#define	KEXEC_MD_PAGES(x)	kexec_md_pages(x)
+
+
+#endif /* _AMD64_KEXEC_H_ */
diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64
index a342242ac66e..e4f01813bc8f 100644
--- a/sys/conf/files.amd64
+++ b/sys/conf/files.amd64
@@ -77,6 +77,8 @@ amd64/amd64/fpu.c		standard
 amd64/amd64/gdb_machdep.c	optional	gdb
 amd64/amd64/initcpu.c		standard
 amd64/amd64/io.c		optional	io
+amd64/amd64/kexec_support.c	standard
+amd64/amd64/kexec_tramp.S	standard
 amd64/amd64/locore.S		standard	no-obj
 amd64/amd64/xen-locore.S	optional	xenhvm \
 	compile-with "${NORMAL_S} -g0" \
diff --git a/sys/x86/include/intr_machdep.h b/sys/x86/include/intr_machdep.h
index 9e913440c712..497c89b0a7eb 100644
--- a/sys/x86/include/intr_machdep.h
+++ b/sys/x86/include/intr_machdep.h
@@ -142,6 +142,7 @@ int	intr_add_handler(struct intsrc *isrc, const char *name,
 int	intr_config_intr(struct intsrc *isrc, enum intr_trigger trig,
     enum intr_polarity pol);
 int	intr_describe(struct intsrc *isrc, void *ih, const char *descr);
+void	intr_disable_all(void);
 void	intr_execute_handlers(struct intsrc *isrc, struct trapframe *frame);
 u_int	intr_next_cpu(int domain);
 struct intsrc *intr_lookup_source(int vector);
diff --git a/sys/x86/x86/intr_machdep.c b/sys/x86/x86/intr_machdep.c
index 023c3df22580..a16d2ced8dba 100644
--- a/sys/x86/x86/intr_machdep.c
+++ b/sys/x86/x86/intr_machdep.c
@@ -245,6 +245,26 @@ intr_register_source(struct intsrc *isrc)
 	return (0);
 }
 
+void
+intr_disable_all(void)
+{
+	/*
+	 * Disable all external interrupts.  This is used by kexec_reboot() to
+	 * prevent problems on the other side when APs are brought up.
+	 */
+	for (int v = 0; v < num_io_irqs; v++) {
+		struct intsrc *is;
+
+		is = interrupt_sources[v];
+		if (is == NULL)
+			continue;
+		if (is->is_pic->pic_disable_intr != NULL) {
+			is->is_pic->pic_disable_source(is, PIC_EOI);
+			is->is_pic->pic_disable_intr(is);
+		}
+	}
+}
+
 struct intsrc *
 intr_lookup_source(int vector)
 {