Subject: PNP BIOS brokenness and proposed fix
To: None <port-i386@netbsd.org>
From: Rafal Boni <rafal@mediaone.net>
List: port-i386
Date: 10/23/2000 15:15:54
[Apologies if my terminology here is slightly muddled... I'm not real
 familiar with the workings of the x86 16-bit protected-mode, especially 
 how it interacts with segmented memory access... That said, I think all
 of this should make sense modulo terminology...]

Folks:
	I've finally tracked down what has been causing NetBSD's PNPBIOS 
	probe to spontaneously reboot my Gateway box[*] and am wondering 
	if the problems with PNPBIOS on the Vaios may have been related.

	The short of the story is: on my GW, the PNPBIOS would occasionally 
	find the need to refer to data in the `Extended BIOS Data Area' (at
	0x400 in physical memory, 0040:0000 in the wonderful x86 segmented
	`architecture' 8-) from within the PNPBIOS code... 

	This would be OK, with two exceptions:
		(1) the PNPBIOS code includes absolute references to 
		    0040:00xx.  This precludes pre-loading a segment
		    register with a GDT and forces you to burn a very
		    specific GDT [see below]

		(2) the PNPBIOS code does not report that it needs the
		    0040:xxxx segment (as it only has the ability to
		    pass back one data segment address and it passes back
		    the code segment as the data segment -- so codeseg ==
		    dataseg == 0xf0000 on my machine).

	Now the nasty part -- with my knowledge of x86 PM programming, the
	only way I could see to get the absolute reference to 0040:xxxx to
	work was to steal the APM BIOS data GDT entry, which either by chance
	or by design maps exactly to 0x0040 hex [**].  I attach a proof of 
	concept patch that solves the mystery reboots on my box, but has
	the problems noted below... I'd like some input on how to properly
	clean up the mess, so please read on...

	However, both APM and PNPBIOS may need this GDT entry at times other
	than startup (APM for BIOS calls from the idle loop, PNPBIOS for 
	dock events, etc. if we're using that).  

	It seems like what is really needed is a common function, callable 
	from both the APM and PNP BIOS handling code (and any other BIOS 
	code in the tree that may need access to the extended BIOS data 
	area) that maps the `magic' [***] GDT to point to the the 0040:xxx
	segment in physical memory; this will also require allocating 
	another GDT slot for the APM data segment (and only mapping it if 
	it's not the already-mapped 0040:xxxx segment)

	The patch is attached at the end of the message -- I'll file a PR
	if I hadn't already on the subject ASAP, but I'd like to hold off
	submitting any code until someone who knowns something (more) about
	all this confirms the sanity of what I'm doing.

Thanks!
--rafal

[*] A Gateway E5200 desktop: Tyan Patriot dual-PIII-capable motherboard
    with AMI BIOS (can't get at rev right now, will check if anyone cares),
    with one processor installed.

[**] GSEL(GAPMDATA_SEL, SEL_KPL) == GSEL(8, 0) == ((8 << 3) | 0) == 0x40

[***] magic in the sense that it maps to 0x0040.

----8<------8<------8<------8<------8<------8<------8<------8<------8<--
Index: i386/apm.c
===================================================================
RCS file: /cvsroot/syssrc/sys/arch/i386/i386/apm.c,v
retrieving revision 1.53
diff -b -u -r1.53 apm.c
--- apm.c       2000/08/13 22:26:27     1.53
+++ apm.c       2000/10/23 18:02:39
@@ -1221,7 +1225,7 @@
                 * descriptor to just the first byte of the code
                 * segment, read only.
                 */
-               setsegment(&gdt[GAPMDATA_SEL].sd,
+               setsegment(&gdt[GEXTBIOSDATA_SEL].sd,
                    ISA_HOLE_VADDR(apminfo.apm_code32_seg_base),
                    0, SDT_MEMROA, SEL_KPL, 0, 0);
        } else if (apminfo.apm_data_seg_base < IOM_BEGIN) {
@@ -1244,12 +1248,12 @@
                    ("mapping bios data area %x @ 0x%lx\n%s: ",
                    apminfo.apm_data_seg_base, memh,
                    apmsc->sc_dev.dv_xname));
-               setsegment(&gdt[GAPMDATA_SEL].sd,
+               setsegment(&gdt[GEXTBIOSDATA_SEL].sd,
                    (void *)memh,
                    apminfo.apm_data_seg_len - 1,
                    SDT_MEMRWA, SEL_KPL, 1, 0);
        } else
-               setsegment(&gdt[GAPMDATA_SEL].sd,
+               setsegment(&gdt[GEXTBIOSDATA_SEL].sd,
                    ISA_HOLE_VADDR(apminfo.apm_data_seg_base),
                    apminfo.apm_data_seg_len - 1,
                    SDT_MEMRWA, SEL_KPL, 1, 0);
Index: include/segments.h
===================================================================     
RCS file: /cvsroot/syssrc/sys/arch/i386/include/segments.h,v
retrieving revision 1.30
diff -b -u -r1.30 segments.h
--- segments.h  1999/11/12 18:37:29     1.30
+++ segments.h  2000/10/23 18:02:41   
@@ -223,7 +223,7 @@
 #define        GUDATA_SEL      5       /* User data descriptor */      
 #define        GAPM32CODE_SEL  6     
 #define        GAPM16CODE_SEL  7     
-#define        GAPMDATA_SEL    8     
+#define GEXTBIOSDATA_SEL 8
 #define        GBIOSCODE_SEL   9     
 #define        GBIOSDATA_SEL   10    
 #define GPNPBIOSCODE_SEL 11
Index: pnpbios/pnpbios.c
===================================================================     
RCS file: /cvsroot/syssrc/sys/arch/i386/pnpbios/pnpbios.c,v
retrieving revision 1.20
diff -b -u -r1.20 pnpbios.c
--- pnpbios.c   2000/06/29 08:44:58     1.20
+++ pnpbios.c   2000/10/23 18:02:46   
@@ -289,7 +293,7 @@
        struct pnpdevnode *dn;
        caddr_t p;
        unsigned int codepbase, datapbase, evaddrp;
-       caddr_t codeva, datava;       
+       caddr_t codeva, datava, ebdava;
        extern char pnpbiostramp[], epnpbiostramp[];
        int res, num, i, size, idx;   
 #ifdef PNPBIOSEVENTS
@@ -337,12 +343,15 @@
                VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE);
        datava = pnpbios_mapit(datapbase, 0x10000,
                VM_PROT_READ | VM_PROT_WRITE);
-       if (codeva == 0 || datava == 0) {
+       ebdava = pnpbios_mapit(0x400, NBPG - 0x400, VM_PROT_READ);  
+       if (codeva == 0 || datava == 0 || ebdava == 0) {
                printf("no vm for mapping\n");
                return;
        }
        pnpbios_scratchbuf = malloc(PNPBIOS_BUFSIZE, M_DEVBUF, M_NOWAIT);

+       setsegment(&gdt[GEXTBIOSDATA_SEL].sd, ebdava, 0xff,
+                  SDT_MEMRWA, SEL_KPL, 0, 0);
        setsegment(&gdt[GPNPBIOSCODE_SEL].sd, codeva, 0xffff,
                   SDT_MEMERA, SEL_KPL, 0, 0);
        setsegment(&gdt[GPNPBIOSDATA_SEL].sd, datava, 0xffff,
----8<------8<------8<------8<------8<------8<------8<------8<------8<--

----
Rafal Boni                                                   rafal@mediaone.net