Re: [tip:x86/trampoline] x86, reboot: Move the real-mode reboot codeto an assembly file

From: Brian Gerst
Date: Fri Feb 18 2011 - 07:31:22 EST


On Fri, Feb 18, 2011 at 12:20 AM, tip-bot for H. Peter Anvin
<hpa@xxxxxxxxxxxxxxx> wrote:
> Commit-ID: Â3d35ac346e981162eeba391e496faceed4753e7b
> Gitweb: Â Â http://git.kernel.org/tip/3d35ac346e981162eeba391e496faceed4753e7b
> Author: Â Â H. Peter Anvin <hpa@xxxxxxxxxxxxxxx>
> AuthorDate: Mon, 14 Feb 2011 18:36:03 -0800
> Committer: ÂH. Peter Anvin <hpa@xxxxxxxxxxxxxxx>
> CommitDate: Thu, 17 Feb 2011 21:05:34 -0800
>
> x86, reboot: Move the real-mode reboot code to an assembly file
>
> Move the real-mode reboot code out to an assembly file (reboot_32.S)
> which is allocated using the common lowmem trampoline allocator.
>
> Signed-off-by: H. Peter Anvin <hpa@xxxxxxxxxxxxxxx>
> LKML-Reference: <4D5DFBE4.7090104@xxxxxxxxx>
> Cc: Stephen Rothwell <sfr@xxxxxxxxxxxxxxxx>
> Cc: Rafael J. Wysocki <rjw@xxxxxxx>
> Cc: Matthieu Castet <castet.matthieu@xxxxxxx>
> ---
> Âarch/x86/include/asm/reboot.h | Â Â5 +-
> Âarch/x86/kernel/Makefile   Â|  Â1 +
> Âarch/x86/kernel/apm_32.c   Â|  12 +----
> Âarch/x86/kernel/reboot.c   Â| Â120 ++++++++-----------------------------
> Âarch/x86/kernel/reboot_32.S Â | Â131 +++++++++++++++++++++++++++++++++++++++++
> Â5 files changed, 162 insertions(+), 107 deletions(-)
>
> diff --git a/arch/x86/include/asm/reboot.h b/arch/x86/include/asm/reboot.h
> index 562d4fd..3250e3d 100644
> --- a/arch/x86/include/asm/reboot.h
> +++ b/arch/x86/include/asm/reboot.h
> @@ -18,7 +18,10 @@ extern struct machine_ops machine_ops;
>
> Âvoid native_machine_crash_shutdown(struct pt_regs *regs);
> Âvoid native_machine_shutdown(void);
> -void machine_real_restart(const unsigned char *code, int length);
> +void machine_real_restart(unsigned int type);
> +/* These must match dispatch_table in reboot_32.S */
> +#define MRR_BIOS Â Â Â 0
> +#define MRR_APM Â Â Â Â Â Â Â Â1
>
> Âtypedef void (*nmi_shootdown_cb)(int, struct die_args*);
> Âvoid nmi_shootdown_cpus(nmi_shootdown_cb callback);
> diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
> index 2e8ce0d..778c5b93 100644
> --- a/arch/x86/kernel/Makefile
> +++ b/arch/x86/kernel/Makefile
> @@ -59,6 +59,7 @@ obj-$(CONFIG_STACKTRACE) Â Â Â+= stacktrace.o
> Âobj-y             Â+= cpu/
> Âobj-y             Â+= acpi/
> Âobj-y             Â+= reboot.o
> +obj-$(CONFIG_X86_32) Â Â Â Â Â += reboot_32.o
> Âobj-$(CONFIG_MCA) Â Â Â Â Â Â Â+= mca_32.o
> Âobj-$(CONFIG_X86_MSR) Â Â Â Â Â+= msr.o
> Âobj-$(CONFIG_X86_CPUID) Â Â Â Â Â Â Â Â+= cpuid.o
> diff --git a/arch/x86/kernel/apm_32.c b/arch/x86/kernel/apm_32.c
> index 0e4f24c..b929108 100644
> --- a/arch/x86/kernel/apm_32.c
> +++ b/arch/x86/kernel/apm_32.c
> @@ -975,20 +975,10 @@ recalc:
>
> Âstatic void apm_power_off(void)
> Â{
> - Â Â Â unsigned char po_bios_call[] = {
> - Â Â Â Â Â Â Â 0xb8, 0x00, 0x10, Â Â Â /* movw Â$0x1000,ax Â*/
> -        0x8e, 0xd0,       /* movw Âax,ss    */
> - Â Â Â Â Â Â Â 0xbc, 0x00, 0xf0, Â Â Â /* movw Â$0xf000,sp Â*/
> - Â Â Â Â Â Â Â 0xb8, 0x07, 0x53, Â Â Â /* movw Â$0x5307,ax Â*/
> - Â Â Â Â Â Â Â 0xbb, 0x01, 0x00, Â Â Â /* movw Â$0x0001,bx Â*/
> - Â Â Â Â Â Â Â 0xb9, 0x03, 0x00, Â Â Â /* movw Â$0x0003,cx Â*/
> -        0xcd, 0x15       Â/* int  $0x15    */
> - Â Â Â };
> -
> Â Â Â Â/* Some bioses don't like being called from CPU != 0 */
> Â Â Â Âif (apm_info.realmode_power_off) {
> Â Â Â Â Â Â Â Âset_cpus_allowed_ptr(current, cpumask_of(0));
> - Â Â Â Â Â Â Â machine_real_restart(po_bios_call, sizeof(po_bios_call));
> + Â Â Â Â Â Â Â machine_real_restart(MRR_APM);
> Â Â Â Â} else {
> Â Â Â Â Â Â Â Â(void)set_system_power_state(APM_STATE_OFF);
> Â Â Â Â}
> diff --git a/arch/x86/kernel/reboot.c b/arch/x86/kernel/reboot.c
> index fc7aae1..10c6619 100644
> --- a/arch/x86/kernel/reboot.c
> +++ b/arch/x86/kernel/reboot.c
> @@ -295,68 +295,16 @@ static int __init reboot_init(void)
> Â}
> Âcore_initcall(reboot_init);
>
> -/* The following code and data reboots the machine by switching to real
> - Â mode and jumping to the BIOS reset entry point, as if the CPU has
> - Â really been reset. ÂThe previous version asked the keyboard
> - Â controller to pulse the CPU reset line, which is more thorough, but
> - Â doesn't work with at least one type of 486 motherboard. ÂIt is easy
> - Â to stop this code working; hence the copious comments. */
> -static const unsigned long long
> -real_mode_gdt_entries [3] =
> -{
> - Â Â Â 0x0000000000000000ULL, Â/* Null descriptor */
> - Â Â Â 0x00009b000000ffffULL, Â/* 16-bit real-mode 64k code at 0x00000000 */
> - Â Â Â 0x000093000100ffffULL Â /* 16-bit real-mode 64k data at 0x00000100 */
> -};
> +extern const unsigned char machine_real_restart_asm[];
> +extern const u64 machine_real_restart_gdt[3];
>
> -static const struct desc_ptr
> -real_mode_gdt = { sizeof (real_mode_gdt_entries) - 1, (long)real_mode_gdt_entries },
> -real_mode_idt = { 0x3ff, 0 };
> -
> -/* This is 16-bit protected mode code to disable paging and the cache,
> - Â switch to real mode and jump to the BIOS reset code.
> -
> - Â The instruction that switches to real mode by writing to CR0 must be
> - Â followed immediately by a far jump instruction, which set CS to a
> - Â valid value for real mode, and flushes the prefetch queue to avoid
> - Â running instructions that have already been decoded in protected
> - Â mode.
> -
> - Â Clears all the flags except ET, especially PG (paging), PE
> - Â (protected-mode enable) and TS (task switch for coprocessor state
> - Â save). ÂFlushes the TLB after paging has been disabled. ÂSets CD and
> - Â NW, to disable the cache on a 486, and invalidates the cache. ÂThis
> - Â is more like the state of a 486 after reset. ÂI don't know if
> - Â something else should be done for other chips.
> -
> - Â More could be done here to set up the registers as if a CPU reset had
> - Â occurred; hopefully real BIOSs don't assume much. */
> -static const unsigned char real_mode_switch [] =
> -{
> -    0x66, 0x0f, 0x20, 0xc0,         /*  Âmovl Â%cr0,%eax    Â*/
> - Â Â Â 0x66, 0x83, 0xe0, 0x11, Â Â Â Â Â Â Â Â /* Â Âandl Â$0x00000011,%eax */
> -    0x66, 0x0d, 0x00, 0x00, 0x00, 0x60,   /*  Âorl  $0x60000000,%eax */
> - Â Â Â 0x66, 0x0f, 0x22, 0xc0, Â Â Â Â Â Â Â Â /* Â Âmovl Â%eax,%cr0 Â Â Â Â*/
> - Â Â Â 0x66, 0x0f, 0x22, 0xd8, Â Â Â Â Â Â Â Â /* Â Âmovl Â%eax,%cr3 Â Â Â Â*/
> -    0x66, 0x0f, 0x20, 0xc3,         /*  Âmovl Â%cr0,%ebx    Â*/
> - Â Â Â 0x66, 0x81, 0xe3, 0x00, 0x00, 0x00, 0x60, Â Â Â /* Â Âandl Â$0x60000000,%ebx */
> -    0x74, 0x02,               /*  Âjz  Âf        Â*/
> -    0x0f, 0x09,               /*  Âwbinvd         */
> -    0x24, 0x10,               /* f: andb Â$0x10,al     */
> - Â Â Â 0x66, 0x0f, 0x22, 0xc0 Â Â Â Â Â Â Â Â Â/* Â Âmovl Â%eax,%cr0 Â Â Â Â*/
> -};
> -static const unsigned char jump_to_bios [] =
> +void machine_real_restart(unsigned int type)
> Â{
> -    0xea, 0x00, 0x00, 0xff, 0xff      Â/*  Âljmp Â$0xffff,$0x0000 Â*/
> -};
> + Â Â Â void *restart_va;
> + Â Â Â unsigned long restart_pa;
> + Â Â Â void (*restart_lowmem)(unsigned int);
> + Â Â Â u64 *lowmem_gdt;
>
> -/*
> - * Switch to real mode and then execute the code
> - * specified by the code and length parameters.
> - * We assume that length will aways be less that 100!
> - */
> -void machine_real_restart(const unsigned char *code, int length)
> -{
> Â Â Â Âlocal_irq_disable();
>
> Â Â Â Â/* Write zero to CMOS register number 0x0f, which the BIOS POST
> @@ -384,41 +332,23 @@ void machine_real_restart(const unsigned char *code, int length)
> Â Â Â Â Â too. */
> Â Â Â Â*((unsigned short *)0x472) = reboot_mode;
>
> - Â Â Â /* For the switch to real mode, copy some code to low memory. ÂIt has
> - Â Â Â Â Âto be in the first 64k because it is running in 16-bit mode, and it
> - Â Â Â Â Âhas to have the same physical and virtual address, because it turns
> - Â Â Â Â Âoff paging. ÂCopy it near the end of the first page, out of the way
> - Â Â Â Â Âof BIOS variables. */
> - Â Â Â memcpy((void *)(0x1000 - sizeof(real_mode_switch) - 100),
> - Â Â Â Â Â Â Â real_mode_switch, sizeof (real_mode_switch));
> - Â Â Â memcpy((void *)(0x1000 - 100), code, length);
> -
> - Â Â Â /* Set up the IDT for real mode. */
> - Â Â Â load_idt(&real_mode_idt);
> -
> - Â Â Â /* Set up a GDT from which we can load segment descriptors for real
> - Â Â Â Â Âmode. ÂThe GDT is not used in real mode; it is just needed here to
> - Â Â Â Â Âprepare the descriptors. */
> - Â Â Â load_gdt(&real_mode_gdt);
> -
> - Â Â Â /* Load the data segment registers, and thus the descriptors ready for
> - Â Â Â Â Âreal mode. ÂThe base address of each segment is 0x100, 16 times the
> - Â Â Â Â Âselector value being loaded here. ÂThis is so that the segment
> - Â Â Â Â Âregisters don't have to be reloaded after switching to real mode:
> - Â Â Â Â Âthe values are consistent for real mode operation already. */
> - Â Â Â __asm__ __volatile__ ("movl $0x0010,%%eax\n"
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\tmovl %%eax,%%ds\n"
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\tmovl %%eax,%%es\n"
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\tmovl %%eax,%%fs\n"
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\tmovl %%eax,%%gs\n"
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\tmovl %%eax,%%ss" : : : "eax");
> -
> - Â Â Â /* Jump to the 16-bit code that we copied earlier. ÂIt disables paging
> - Â Â Â Â Âand the cache, switches to real mode, and jumps to the BIOS reset
> - Â Â Â Â Âentry point. */
> - Â Â Â __asm__ __volatile__ ("ljmp $0x0008,%0"
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â :
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â : "i" ((void *)(0x1000 - sizeof (real_mode_switch) - 100)));
> + Â Â Â /* Patch the GDT in the low memory trampoline */
> + Â Â Â lowmem_gdt = TRAMPOLINE_SYM(machine_real_restart_gdt);
> +
> + Â Â Â restart_va = TRAMPOLINE_SYM(machine_real_restart_asm);
> + Â Â Â restart_pa = virt_to_phys(restart_va);
> + Â Â Â restart_lowmem = (void (*)(unsigned int))restart_pa;
> +
> + Â Â Â /* GDT[0]: GDT self-pointer */
> + Â Â Â lowmem_gdt[0] =
> + Â Â Â Â Â Â Â (u64)(sizeof(machine_real_restart_gdt) - 1) +
> + Â Â Â Â Â Â Â ((u64)virt_to_phys(lowmem_gdt) << 16);
> + Â Â Â /* GDT[1]: 64K real mode code segment */
> + Â Â Â lowmem_gdt[1] =
> + Â Â Â Â Â Â Â GDT_ENTRY(0x009b, restart_pa, 0xffff);
> +
> + Â Â Â /* Jump to the identity-mapped low memory code */
> + Â Â Â restart_lowmem(type);
> Â}
> Â#ifdef CONFIG_APM_MODULE
> ÂEXPORT_SYMBOL(machine_real_restart);
> @@ -573,7 +503,7 @@ static void native_machine_emergency_restart(void)
>
> Â#ifdef CONFIG_X86_32
> Â Â Â Â Â Â Â Âcase BOOT_BIOS:
> - Â Â Â Â Â Â Â Â Â Â Â machine_real_restart(jump_to_bios, sizeof(jump_to_bios));
> + Â Â Â Â Â Â Â Â Â Â Â machine_real_restart(MRR_BIOS);
>
> Â Â Â Â Â Â Â Â Â Â Â Âreboot_type = BOOT_KBD;
> Â Â Â Â Â Â Â Â Â Â Â Âbreak;
> diff --git a/arch/x86/kernel/reboot_32.S b/arch/x86/kernel/reboot_32.S
> new file mode 100644
> index 0000000..f242356
> --- /dev/null
> +++ b/arch/x86/kernel/reboot_32.S
> @@ -0,0 +1,131 @@
> +#include <linux/linkage.h>
> +#include <linux/init.h>
> +#include <asm/segment.h>
> +#include <asm/page_types.h>
> +
> +/*
> + * The following code and data reboots the machine by switching to real
> + * mode and jumping to the BIOS reset entry point, as if the CPU has
> + * really been reset. ÂThe previous version asked the keyboard
> + * controller to pulse the CPU reset line, which is more thorough, but
> + * doesn't work with at least one type of 486 motherboard. ÂIt is easy
> + * to stop this code working; hence the copious comments.
> + *
> + * This code is called with the restart type (0 = BIOS, 1 = APM) in %eax.
> + */
> + Â Â Â .section ".x86_trampoline","a"
> + Â Â Â .balign 16
> + Â Â Â .code32
> +ENTRY(machine_real_restart_asm)
> +r_base = .
> + Â Â Â /* Get our own relocated address */
> +    call  Â1f
> +1:   popl  Â%ebx
> +    subl  Â$1b, %ebx
> +
> + Â Â Â /* Patch post-real-mode segment jump */
> +    movw  Âdispatch_table(%ebx,%ecx,2),%cx
> +    movw  Â%cx, 101f(%ebx)
> +    movw  Â%ax, 102f(%ebx)

This looks wrong. The type parameter is in %eax, not %ecx, and this
code is expecting %eax to be a segment.

--
Brian Gerst
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/