[PATCH v1 02/14] x86/realmode: make %gs == 0 an invariant

H. Peter Anvin posted 14 patches 2 weeks, 5 days ago
[PATCH v1 02/14] x86/realmode: make %gs == 0 an invariant
Posted by H. Peter Anvin 2 weeks, 5 days ago
When accessing data that is not "near", either only one segment is
used or one segment is always zero. Leave %gs == 0 at all times
throughout the C code; this reduces the number of segment loads
needed.

Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com>
---
 arch/x86/boot/a20.c               | 11 +++++------
 arch/x86/boot/header.S            |  3 +++
 arch/x86/boot/regs.c              |  3 ++-
 arch/x86/boot/video-bios.c        |  5 ++---
 arch/x86/boot/video-mode.c        |  5 ++---
 arch/x86/boot/video.c             |  7 +++----
 arch/x86/realmode/rm/wakeup_asm.S | 10 ++++++----
 7 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/arch/x86/boot/a20.c b/arch/x86/boot/a20.c
index bda042933a05..3ab6cd8eaa31 100644
--- a/arch/x86/boot/a20.c
+++ b/arch/x86/boot/a20.c
@@ -56,20 +56,19 @@ static int a20_test(int loops)
 	int ok = 0;
 	int saved, ctr;
 
-	set_fs(0x0000);
-	set_gs(0xffff);
+	set_fs(0xffff);
 
-	saved = ctr = rdfs32(A20_TEST_ADDR);
+	saved = ctr = rdgs32(A20_TEST_ADDR);
 
 	while (loops--) {
-		wrfs32(++ctr, A20_TEST_ADDR);
+		wrgs32(++ctr, A20_TEST_ADDR);
 		io_delay();	/* Serialize and make delay constant */
-		ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;
+		ok = rdfs32(A20_TEST_ADDR+0x10) ^ ctr;
 		if (ok)
 			break;
 	}
 
-	wrfs32(saved, A20_TEST_ADDR);
+	wrgs32(saved, A20_TEST_ADDR);
 	return ok;
 }
 
diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S
index 9bea5a1e2c52..bda20395658f 100644
--- a/arch/x86/boot/header.S
+++ b/arch/x86/boot/header.S
@@ -596,6 +596,9 @@ start_of_setup:
 	shrw	$2, %cx
 	rep stosl
 
+# The C code uses %gs == 0 as invariant
+	movw	%ax, %gs
+
 # Jump to C code (should not return)
 	calll	main
 
diff --git a/arch/x86/boot/regs.c b/arch/x86/boot/regs.c
index 55de6b3092b8..54d6dfd129c5 100644
--- a/arch/x86/boot/regs.c
+++ b/arch/x86/boot/regs.c
@@ -22,6 +22,7 @@ void initregs(struct biosregs *reg)
 	reg->eflags |= X86_EFLAGS_CF;
 	reg->ds = ds();
 	reg->es = ds();
+	/* The input values of %cs and %ss are ignored by intcall() */
 	reg->fs = fs();
-	reg->gs = gs();
+	/* %gs == 0 */
 }
diff --git a/arch/x86/boot/video-bios.c b/arch/x86/boot/video-bios.c
index 6eb8c06bc287..e8be64424a40 100644
--- a/arch/x86/boot/video-bios.c
+++ b/arch/x86/boot/video-bios.c
@@ -73,7 +73,6 @@ static int bios_probe(void)
 	if (adapter != ADAPTER_EGA && adapter != ADAPTER_VGA)
 		return 0;
 
-	set_fs(0);
 	crtc = vga_crtc();
 
 	video_bios.modes = GET_HEAP(struct mode_info, 0);
@@ -105,8 +104,8 @@ static int bios_probe(void)
 		mi = GET_HEAP(struct mode_info, 1);
 		mi->mode = VIDEO_FIRST_BIOS+mode;
 		mi->depth = 0;	/* text */
-		mi->x = rdfs16(0x44a);
-		mi->y = rdfs8(0x484)+1;
+		mi->x = rdgs16(0x44a);
+		mi->y = rdgs8(0x484)+1;
 		nmodes++;
 	}
 
diff --git a/arch/x86/boot/video-mode.c b/arch/x86/boot/video-mode.c
index 9ada55dc1ab7..e5b9bc96bd42 100644
--- a/arch/x86/boot/video-mode.c
+++ b/arch/x86/boot/video-mode.c
@@ -119,9 +119,8 @@ static void vga_recalc_vertical(void)
 	u16 crtc;
 	u8 pt, ov;
 
-	set_fs(0);
-	font_size = rdfs8(0x485); /* BIOS: font size (pixels) */
-	rows = force_y ? force_y : rdfs8(0x484)+1; /* Text rows */
+	font_size = rdgs8(0x485); /* BIOS: font size (pixels) */
+	rows = force_y ? force_y : rdgs8(0x484)+1; /* Text rows */
 
 	rows *= font_size;	/* Visible scan lines */
 	rows--;			/* ... minus one */
diff --git a/arch/x86/boot/video.c b/arch/x86/boot/video.c
index 0641c8c46aee..09b810faa5c0 100644
--- a/arch/x86/boot/video.c
+++ b/arch/x86/boot/video.c
@@ -79,12 +79,11 @@ static void store_mode_params(void)
 		video_segment = 0xb800;
 	}
 
-	set_fs(0);
-	font_size = rdfs16(0x485); /* Font size, BIOS area */
+	font_size = rdgs16(0x485); /* Font size, BIOS area */
 	boot_params.screen_info.orig_video_points = font_size;
 
-	x = rdfs16(0x44a);
-	y = (adapter == ADAPTER_CGA) ? 25 : rdfs8(0x484)+1;
+	x = rdgs16(0x44a);
+	y = (adapter == ADAPTER_CGA) ? 25 : rdgs8(0x484)+1;
 
 	if (force_x)
 		x = force_x;
diff --git a/arch/x86/realmode/rm/wakeup_asm.S b/arch/x86/realmode/rm/wakeup_asm.S
index 02d0ba16ae33..a8a8580158d7 100644
--- a/arch/x86/realmode/rm/wakeup_asm.S
+++ b/arch/x86/realmode/rm/wakeup_asm.S
@@ -70,15 +70,17 @@ SYM_CODE_START(wakeup_start)
 	movl	$rm_stack_end, %esp
 	movw	%ax, %ds
 	movw	%ax, %es
-	movw	%ax, %fs
-	movw	%ax, %gs
 
-	lidtl	.Lwakeup_idt
+	xorl	%eax, %eax
+	movw	%ax, %fs
+	movw	%ax, %gs	/* The real mode code requires %gs == 0 */
 
 	/* Clear the EFLAGS */
-	pushl $0
+	pushl	%eax
 	popfl
 
+	lidtl	.Lwakeup_idt
+
 	/* Check header signature... */
 	movl	signature, %eax
 	cmpl	$WAKEUP_HEADER_SIGNATURE, %eax
-- 
2.52.0
Re: [PATCH v1 02/14] x86/realmode: make %gs == 0 an invariant
Posted by Uros Bizjak 2 weeks, 5 days ago
On Tue, Jan 20, 2026 at 8:54 PM H. Peter Anvin <hpa@zytor.com> wrote:
>
> When accessing data that is not "near", either only one segment is
> used or one segment is always zero. Leave %gs == 0 at all times
> throughout the C code; this reduces the number of segment loads
> needed.
>
> Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com>

Reviewed-by: Uros Bizjak <ubizjak@gmail.com>

> ---
>  arch/x86/boot/a20.c               | 11 +++++------
>  arch/x86/boot/header.S            |  3 +++
>  arch/x86/boot/regs.c              |  3 ++-
>  arch/x86/boot/video-bios.c        |  5 ++---
>  arch/x86/boot/video-mode.c        |  5 ++---
>  arch/x86/boot/video.c             |  7 +++----
>  arch/x86/realmode/rm/wakeup_asm.S | 10 ++++++----
>  7 files changed, 23 insertions(+), 21 deletions(-)
>
> diff --git a/arch/x86/boot/a20.c b/arch/x86/boot/a20.c
> index bda042933a05..3ab6cd8eaa31 100644
> --- a/arch/x86/boot/a20.c
> +++ b/arch/x86/boot/a20.c
> @@ -56,20 +56,19 @@ static int a20_test(int loops)
>         int ok = 0;
>         int saved, ctr;
>
> -       set_fs(0x0000);
> -       set_gs(0xffff);
> +       set_fs(0xffff);
>
> -       saved = ctr = rdfs32(A20_TEST_ADDR);
> +       saved = ctr = rdgs32(A20_TEST_ADDR);
>
>         while (loops--) {
> -               wrfs32(++ctr, A20_TEST_ADDR);
> +               wrgs32(++ctr, A20_TEST_ADDR);
>                 io_delay();     /* Serialize and make delay constant */
> -               ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;
> +               ok = rdfs32(A20_TEST_ADDR+0x10) ^ ctr;
>                 if (ok)
>                         break;
>         }
>
> -       wrfs32(saved, A20_TEST_ADDR);
> +       wrgs32(saved, A20_TEST_ADDR);
>         return ok;
>  }
>
> diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S
> index 9bea5a1e2c52..bda20395658f 100644
> --- a/arch/x86/boot/header.S
> +++ b/arch/x86/boot/header.S
> @@ -596,6 +596,9 @@ start_of_setup:
>         shrw    $2, %cx
>         rep stosl
>
> +# The C code uses %gs == 0 as invariant
> +       movw    %ax, %gs
> +
>  # Jump to C code (should not return)
>         calll   main
>
> diff --git a/arch/x86/boot/regs.c b/arch/x86/boot/regs.c
> index 55de6b3092b8..54d6dfd129c5 100644
> --- a/arch/x86/boot/regs.c
> +++ b/arch/x86/boot/regs.c
> @@ -22,6 +22,7 @@ void initregs(struct biosregs *reg)
>         reg->eflags |= X86_EFLAGS_CF;
>         reg->ds = ds();
>         reg->es = ds();
> +       /* The input values of %cs and %ss are ignored by intcall() */
>         reg->fs = fs();
> -       reg->gs = gs();
> +       /* %gs == 0 */
>  }
> diff --git a/arch/x86/boot/video-bios.c b/arch/x86/boot/video-bios.c
> index 6eb8c06bc287..e8be64424a40 100644
> --- a/arch/x86/boot/video-bios.c
> +++ b/arch/x86/boot/video-bios.c
> @@ -73,7 +73,6 @@ static int bios_probe(void)
>         if (adapter != ADAPTER_EGA && adapter != ADAPTER_VGA)
>                 return 0;
>
> -       set_fs(0);
>         crtc = vga_crtc();
>
>         video_bios.modes = GET_HEAP(struct mode_info, 0);
> @@ -105,8 +104,8 @@ static int bios_probe(void)
>                 mi = GET_HEAP(struct mode_info, 1);
>                 mi->mode = VIDEO_FIRST_BIOS+mode;
>                 mi->depth = 0;  /* text */
> -               mi->x = rdfs16(0x44a);
> -               mi->y = rdfs8(0x484)+1;
> +               mi->x = rdgs16(0x44a);
> +               mi->y = rdgs8(0x484)+1;
>                 nmodes++;
>         }
>
> diff --git a/arch/x86/boot/video-mode.c b/arch/x86/boot/video-mode.c
> index 9ada55dc1ab7..e5b9bc96bd42 100644
> --- a/arch/x86/boot/video-mode.c
> +++ b/arch/x86/boot/video-mode.c
> @@ -119,9 +119,8 @@ static void vga_recalc_vertical(void)
>         u16 crtc;
>         u8 pt, ov;
>
> -       set_fs(0);
> -       font_size = rdfs8(0x485); /* BIOS: font size (pixels) */
> -       rows = force_y ? force_y : rdfs8(0x484)+1; /* Text rows */
> +       font_size = rdgs8(0x485); /* BIOS: font size (pixels) */
> +       rows = force_y ? force_y : rdgs8(0x484)+1; /* Text rows */
>
>         rows *= font_size;      /* Visible scan lines */
>         rows--;                 /* ... minus one */
> diff --git a/arch/x86/boot/video.c b/arch/x86/boot/video.c
> index 0641c8c46aee..09b810faa5c0 100644
> --- a/arch/x86/boot/video.c
> +++ b/arch/x86/boot/video.c
> @@ -79,12 +79,11 @@ static void store_mode_params(void)
>                 video_segment = 0xb800;
>         }
>
> -       set_fs(0);
> -       font_size = rdfs16(0x485); /* Font size, BIOS area */
> +       font_size = rdgs16(0x485); /* Font size, BIOS area */
>         boot_params.screen_info.orig_video_points = font_size;
>
> -       x = rdfs16(0x44a);
> -       y = (adapter == ADAPTER_CGA) ? 25 : rdfs8(0x484)+1;
> +       x = rdgs16(0x44a);
> +       y = (adapter == ADAPTER_CGA) ? 25 : rdgs8(0x484)+1;
>
>         if (force_x)
>                 x = force_x;
> diff --git a/arch/x86/realmode/rm/wakeup_asm.S b/arch/x86/realmode/rm/wakeup_asm.S
> index 02d0ba16ae33..a8a8580158d7 100644
> --- a/arch/x86/realmode/rm/wakeup_asm.S
> +++ b/arch/x86/realmode/rm/wakeup_asm.S
> @@ -70,15 +70,17 @@ SYM_CODE_START(wakeup_start)
>         movl    $rm_stack_end, %esp
>         movw    %ax, %ds
>         movw    %ax, %es
> -       movw    %ax, %fs
> -       movw    %ax, %gs
>
> -       lidtl   .Lwakeup_idt
> +       xorl    %eax, %eax
> +       movw    %ax, %fs
> +       movw    %ax, %gs        /* The real mode code requires %gs == 0 */
>
>         /* Clear the EFLAGS */
> -       pushl $0
> +       pushl   %eax
>         popfl
>
> +       lidtl   .Lwakeup_idt
> +
>         /* Check header signature... */
>         movl    signature, %eax
>         cmpl    $WAKEUP_HEADER_SIGNATURE, %eax
> --
> 2.52.0
>
Re: [PATCH v1 02/14] x86/realmode: make %gs == 0 an invariant
Posted by David Laight 2 weeks, 5 days ago
On Tue, 20 Jan 2026 11:53:54 -0800
"H. Peter Anvin" <hpa@zytor.com> wrote:

> When accessing data that is not "near", either only one segment is
> used or one segment is always zero. Leave %gs == 0 at all times
> throughout the C code; this reduces the number of segment loads
> needed.
...
> diff --git a/arch/x86/boot/a20.c b/arch/x86/boot/a20.c
> index bda042933a05..3ab6cd8eaa31 100644
> --- a/arch/x86/boot/a20.c
> +++ b/arch/x86/boot/a20.c
> @@ -56,20 +56,19 @@ static int a20_test(int loops)
>  	int ok = 0;
>  	int saved, ctr;
>  
> -	set_fs(0x0000);
> -	set_gs(0xffff);
> +	set_fs(0xffff);
>  
> -	saved = ctr = rdfs32(A20_TEST_ADDR);
> +	saved = ctr = rdgs32(A20_TEST_ADDR);

Would it be better to wrap that as (say) read_abs_32() since the
objective is to read an absolute address (using the relevant zero segment
register) rather than to read though either fs or gs.

Is this code all running in 32bit linear mode with non-zero cs/ss/ds
segment registers and 'suitable' entries in the GDT?
That mode makes my brain hurt :-)
Or is there a fudge to get 16bit asm from the C.

	David
Re: [PATCH v1 02/14] x86/realmode: make %gs == 0 an invariant
Posted by H. Peter Anvin 2 weeks, 5 days ago
On 2026-01-20 14:40, David Laight wrote:
> 
> Would it be better to wrap that as (say) read_abs_32() since the
> objective is to read an absolute address (using the relevant zero segment
> register) rather than to read though either fs or gs.
> 
> Is this code all running in 32bit linear mode with non-zero cs/ss/ds
> segment registers and 'suitable' entries in the GDT?
> That mode makes my brain hurt :-)
> Or is there a fudge to get 16bit asm from the C.
> 

To address your specific question, no I don't think that is a good idea, since
it would imply that you can reach an arbitrary linear address, which is
definitely not the case.

At least this way the user has to look at what they are doing, which I think
is a good thing for this specific code.

	-hpa
Re: [PATCH v1 02/14] x86/realmode: make %gs == 0 an invariant
Posted by H. Peter Anvin 2 weeks, 5 days ago
On 2026-01-20 14:40, David Laight wrote:
> 
> Would it be better to wrap that as (say) read_abs_32() since the
> objective is to read an absolute address (using the relevant zero segment
> register) rather than to read though either fs or gs.
> 
> Is this code all running in 32bit linear mode with non-zero cs/ss/ds
> segment registers and 'suitable' entries in the GDT?
> That mode makes my brain hurt :-)
> Or is there a fudge to get 16bit asm from the C.
> 

The code is running in 16-bit real mode, compiled using -m16.

	-hpa