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