root/sys/arch/amd64/amd64/kexec_subr.S
/*      $OpenBSD: kexec_subr.S,v 1.3 2026/02/05 06:22:40 jsg Exp $      */
/*
 * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net>
 * Copyright (c) 2025 Hans-Joerg Hoexer <hshoexer@yerbouti.franken.de>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* kexec trampoline, based on sys/arch/amd64/stand/efiboot/run_i386.S */

#include <machine/asm.h>
#include <machine/param.h>
#include <machine/specialreg.h>
#include <machine/kexec.h>

#define CODE_SEGMENT    0x10
#define DATA_SEGMENT    0x18

        .globl  kexec_size
kexec_size:
        .long   kexec_end - kexec_tramp

        .align  NBPG,0xcc
        .text
        .globl  kexec_tramp
kexec_tramp:
        /*
         * %rdi: VA of old kernel to be overwritten
         * %rsi: VA of new kernel
         * %rdx: entry point of new kernel (PA)
         * %rcx: kernel size
         * %r8: VA stack pointer
         */

        /* use %r8 as "anchor" */
        leaq    -7(%rip), %r9   /* size of leaq instruction is 7 */

        /* save entry PA on local stack */
        movq    %rdx, (kexec_end - kexec_tramp - 0x8)(%r9)

        /* about to overwrite boot kernel, thus disable interrupts */
        cli

        /*
         * %rsi: from, VA new kernel
         * %rdi: to, VA old kernel
         * %rcx: count
         */
        cld
        movq    %rcx, (kexec_end - kexec_tramp - 0x10)(%r9)
        shrq    $3,%rcx
        rep
        movsq                   /* copy 64bit words */

        movq    (kexec_end - kexec_tramp - 0x10)(%r9),%rcx
        andl    $7,%ecx         /* any bytes left? */
        rep
        movsb                   /* copy remaining bytes */

        /* boot kernel is now overwritten */

        /* get new kernel entry PA from local stack */
        movq    (kexec_end - kexec_tramp - 0x8)(%r9), %rsi

        /* patch in ljmp target */
        movq    $KEXEC_TRAMPOLINE, %rax
        lea     (kexec32a - kexec_tramp)(%rax), %rax
        movl    %eax, (kexec32r - kexec_tramp)(%r9)

        /* boot arguments are provided on the stack*/
        movq    %r8, %rsp

        lea     (gdt - kexec_tramp)(%r9),%rax
        mov     %rax, (gdtrr - kexec_tramp)(%r9)
        lgdt    (gdtr - kexec_tramp)(%r9)

        ljmp    *(kexec32r - kexec_tramp)(%r9)

        .align  4
kexec32a:
        .code32                         /* back in compatibility mode */
        movl    $DATA_SEGMENT, %eax
        movl    %eax, %ds
        movl    %eax, %es
        movl    %eax, %fs
        movl    %eax, %gs
        movl    %eax, %ss

        movl    %cr0, %eax
        andl    $(~CR0_PG), %eax
        movl    %eax, %cr0              /* clears LMA (long mode active) */

        movl    %cr4, %eax
        andl    $(~CR4_PAE), %eax
        movl    %eax, %cr4

        movl    $MSR_EFER, %ecx
        rdmsr
        andl    $(~EFER_LME), %eax      /* disable long mode */
        wrmsr

        jmp     kexec32b
kexec32b:
        .code32                         /* back in legacy mode */

        call    *%esi                   /* jump into new kernel */

        .align  4
kexec32r:
        .long   0
        .long   CODE_SEGMENT
        .align  4
gdt:
        .long   0, 0
        .long   0, 0
        /* 32bit code segment */
        .byte   0xff, 0xff, 0x00, 0x00, 0x00, 0x9f, 0xcf, 0x00
        /* 32bit data segment */
        .byte   0xff, 0xff, 0x00, 0x00, 0x00, 0x93, 0xcf, 0x00
gdtr:
        .word   gdtr - gdt
gdtrr:
        .quad   0

        .align  8
        /* local stack */
        .space  1024
kexec_end: