c784f85ceb71ef64cf012e338bb6c82c24222e88
[dragonfly.git] / sys / platform / pc32 / i386 / bios.c
1 /*-
2  * Copyright (c) 1997 Michael Smith
3  * Copyright (c) 1998 Jonathan Lemon
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD: src/sys/i386/i386/bios.c,v 1.29.2.3 2001/07/19 18:07:35 imp Exp $
28  * $DragonFly: src/sys/platform/pc32/i386/bios.c,v 1.10 2004/04/29 12:11:16 joerg Exp $
29  */
30
31 /*
32  * Code for dealing with the BIOS in x86 PC systems.
33  */
34
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/kernel.h>
38 #include <vm/vm.h>
39 #include <vm/pmap.h>
40 #include <machine/md_var.h>
41 #include <machine/globaldata.h>
42 #include <machine/pc/bios.h>
43
44 #define BIOS_START      0xe0000
45 #define BIOS_SIZE       0x20000
46
47 /* exported lookup results */
48 struct bios32_SDentry           PCIbios;
49 struct PnPBIOS_table            *PnPBIOStable;
50
51 static u_int                    bios32_SDCI;
52
53 /* start fairly early */
54 static void                     bios32_init(void *junk);
55 SYSINIT(bios32, SI_SUB_CPU, SI_ORDER_ANY, bios32_init, NULL);
56
57 /*
58  * bios32_init
59  *
60  * Locate various bios32 entities.
61  */
62 static void
63 bios32_init(void *junk)
64 {
65     u_long                      sigaddr;
66     struct bios32_SDheader      *sdh;
67     struct PnPBIOS_table        *pt;
68     uint8_t                     ck, *cv;
69     int                         i;
70     char                        *p;
71     
72     /*
73      * BIOS32 Service Directory, PCI BIOS
74      */
75     
76     /* look for the signature */
77     if ((sigaddr = bios_sigsearch(0, "_32_", 4, 16, 0)) != 0) {
78
79         /* get a virtual pointer to the structure */
80         sdh = (struct bios32_SDheader *)(uintptr_t)BIOS_PADDRTOVADDR(sigaddr);
81         for (cv = (uint8_t *)sdh, ck = 0, i = 0; i < (sdh->len * 16); i++) {
82             ck += cv[i];
83         }
84         /* If checksum is OK, enable use of the entrypoint */
85         if ((ck == 0) && BIOS_START <= sdh->entry &&
86             (sdh->entry < (BIOS_START + BIOS_SIZE))
87         ) {
88             bios32_SDCI = BIOS_PADDRTOVADDR(sdh->entry);
89             if (bootverbose) {
90                 printf("bios32: Found BIOS32 Service Directory header at %p\n", sdh);
91                 printf("bios32: Entry = 0x%x (%x)  Rev = %d  Len = %d\n", 
92                        sdh->entry, bios32_SDCI, sdh->revision, sdh->len);
93             }
94
95             /*  Allow user override of PCI BIOS search */
96             if (((p = getenv("machdep.bios.pci")) == NULL) || strcmp(p, "disable")) {
97                 /* See if there's a PCI BIOS entrypoint here */
98                 PCIbios.ident.id = 0x49435024;  /* PCI systems should have this */
99                 if (!bios32_SDlookup(&PCIbios) && bootverbose)
100                     printf("pcibios: PCI BIOS entry at 0x%x\n", PCIbios.entry);
101             }
102             if (p != NULL)
103                 freeenv(p);
104         } else {
105             printf("bios32: Bad BIOS32 Service Directory\n");
106         }
107     }
108
109     /*
110      * PnP BIOS
111      *
112      * Allow user override of PnP BIOS search
113      */
114     if (((p = getenv("machdep.bios.pnp")) == NULL || strcmp(p, "disable")) &&
115         (sigaddr = bios_sigsearch(0, "$PnP", 4, 16, 0)) != 0
116     ) {
117         /* get a virtual pointer to the structure */
118         pt = (struct PnPBIOS_table *)(uintptr_t)BIOS_PADDRTOVADDR(sigaddr);
119         for (cv = (uint8_t *)pt, ck = 0, i = 0; i < pt->len; i++) {
120             ck += cv[i];
121         }
122         /* If checksum is OK, enable use of the entrypoint */
123         if (ck == 0) {
124             PnPBIOStable = pt;
125             if (bootverbose) {
126                 printf("pnpbios: Found PnP BIOS data at %p\n", pt);
127                 printf("pnpbios: Entry = %x:%x  Rev = %d.%d\n", 
128                        pt->pmentrybase, pt->pmentryoffset, pt->version >> 4, pt->version & 0xf);
129                 if ((pt->control & 0x3) == 0x01)
130                     printf("pnpbios: Event flag at %x\n", pt->evflagaddr);
131                 if (pt->oemdevid != 0)
132                     printf("pnpbios: OEM ID %x\n", pt->oemdevid);
133                 
134             }
135         } else {
136             printf("pnpbios: Bad PnP BIOS data checksum\n");
137         }
138     }
139     if (p != NULL)
140         freeenv(p);
141     if (bootverbose) {
142             /* look for other know signatures */
143             printf("Other BIOS signatures found:\n");
144     }
145 }
146
147 /*
148  * bios32_SDlookup
149  *
150  * Query the BIOS32 Service Directory for the service named in (ent),
151  * returns nonzero if the lookup fails.  The caller must fill in
152  * (ent->ident), the remainder are populated on a successful lookup.
153  */
154 int
155 bios32_SDlookup(struct bios32_SDentry *ent)
156 {
157     struct bios_regs args;
158
159     if (bios32_SDCI == 0)
160         return (1);
161
162     args.eax = ent->ident.id;           /* set up arguments */
163     args.ebx = args.ecx = args.edx = 0;
164     bios32(&args, bios32_SDCI, GSEL(GCODE_SEL, SEL_KPL));
165     if ((args.eax & 0xff) == 0) {       /* success? */
166         ent->base = args.ebx;
167         ent->len = args.ecx;
168         ent->entry = args.edx;
169         ent->ventry = BIOS_PADDRTOVADDR(ent->base + ent->entry);
170         return (0);                     /* all OK */
171     }
172     return (1);                         /* failed */
173 }
174
175
176 /*
177  * bios_sigsearch
178  *
179  * Search some or all of the BIOS region for a signature string.
180  *
181  * (start)      Optional offset returned from this function 
182  *              (for searching for multiple matches), or NULL
183  *              to start the search from the base of the BIOS.
184  *              Note that this will be a _physical_ address in
185  *              the range 0xe0000 - 0xfffff.
186  * (sig)        is a pointer to the byte(s) of the signature.
187  * (siglen)     number of bytes in the signature.
188  * (paralen)    signature paragraph (alignment) size.
189  * (sigofs)     offset of the signature within the paragraph.
190  *
191  * Returns the _physical_ address of the found signature, 0 if the
192  * signature was not found.
193  */
194
195 uint32_t
196 bios_sigsearch(uint32_t start, u_char *sig, int siglen, int paralen, int sigofs)
197 {
198     u_char      *sp, *end;
199     
200     /* compute the starting address */
201     if ((start >= BIOS_START) && (start <= (BIOS_START + BIOS_SIZE))) {
202         sp = (char *)BIOS_PADDRTOVADDR(start);
203     } else if (start == 0) {
204         sp = (char *)BIOS_PADDRTOVADDR(BIOS_START);
205     } else {
206         return 0;                               /* bogus start address */
207     }
208
209     /* compute the end address */
210     end = (u_char *)BIOS_PADDRTOVADDR(BIOS_START + BIOS_SIZE);
211
212     /* loop searching */
213     while ((sp + sigofs + siglen) < end) {
214         
215         /* compare here */
216         if (!bcmp(sp + sigofs, sig, siglen)) {
217             /* convert back to physical address */
218             return((uint32_t)BIOS_VADDRTOPADDR(sp));
219         }
220         sp += paralen;
221     }
222     return(0);
223 }
224
225 /*
226  * do not staticize, used by bioscall.s
227  */
228 union {
229     struct {
230         uint16_t offset;
231         uint16_t segment;
232     } vec16;
233     struct {
234         uint32_t offset;
235         uint16_t segment;
236     } vec32;
237 } bioscall_vector;                      /* bios jump vector */
238
239 void
240 set_bios_selectors(struct bios_segments *seg, int flags)
241 {
242     struct soft_segment_descriptor ssd = {
243         0,                      /* segment base address (overwritten) */
244         0,                      /* length (overwritten) */
245         SDT_MEMERA,             /* segment type (overwritten) */
246         0,                      /* priority level */
247         1,                      /* descriptor present */
248         0, 0,
249         1,                      /* descriptor size (overwritten) */
250         0                       /* granularity == byte units */
251     };
252     union descriptor *p_gdt;
253
254     p_gdt = &gdt[mycpu->gd_cpuid * NGDT];
255
256     ssd.ssd_base = seg->code32.base;
257     ssd.ssd_limit = seg->code32.limit;
258     ssdtosd(&ssd, &p_gdt[GBIOSCODE32_SEL].sd);
259
260     ssd.ssd_def32 = 0;
261     if (flags & BIOSCODE_FLAG) {
262         ssd.ssd_base = seg->code16.base;
263         ssd.ssd_limit = seg->code16.limit;
264         ssdtosd(&ssd, &p_gdt[GBIOSCODE16_SEL].sd);
265     }
266
267     ssd.ssd_type = SDT_MEMRWA;
268     if (flags & BIOSDATA_FLAG) {
269         ssd.ssd_base = seg->data.base;
270         ssd.ssd_limit = seg->data.limit;
271         ssdtosd(&ssd, &p_gdt[GBIOSDATA_SEL].sd);
272     }
273
274     if (flags & BIOSUTIL_FLAG) {
275         ssd.ssd_base = seg->util.base;
276         ssd.ssd_limit = seg->util.limit;
277         ssdtosd(&ssd, &p_gdt[GBIOSUTIL_SEL].sd);
278     }
279
280     if (flags & BIOSARGS_FLAG) {
281         ssd.ssd_base = seg->args.base;
282         ssd.ssd_limit = seg->args.limit;
283         ssdtosd(&ssd, &p_gdt[GBIOSARGS_SEL].sd);
284     }
285 }
286
287 extern int vm86pa;
288 extern void bios16_jmp(void);
289
290 /*
291  * this routine is really greedy with selectors, and uses 5:
292  *
293  * 32-bit code selector:        to return to kernel
294  * 16-bit code selector:        for running code
295  *        data selector:        for 16-bit data
296  *        util selector:        extra utility selector
297  *        args selector:        to handle pointers
298  *
299  * the util selector is set from the util16 entry in bios16_args, if a
300  * "U" specifier is seen.
301  *
302  * See <machine/pc/bios.h> for description of format specifiers
303  */
304 int
305 bios16(struct bios_args *args, char *fmt, ...)
306 {
307     char        *p, *stack, *stack_top;
308     __va_list   ap;
309     int         flags = BIOSCODE_FLAG | BIOSDATA_FLAG;
310     u_int       i, arg_start, arg_end;
311     pt_entry_t  *pte;
312     pd_entry_t  *ptd;
313
314     arg_start = 0xffffffff;
315     arg_end = 0;
316
317     /*
318      * Some BIOS entrypoints attempt to copy the largest-case
319      * argument frame (in order to generalise handling for 
320      * different entry types).  If our argument frame is 
321      * smaller than this, the BIOS will reach off the top of
322      * our constructed stack segment.  Pad the top of the stack
323      * with some garbage to avoid this.
324      */
325     stack = (caddr_t)PAGE_SIZE - 32;
326
327     __va_start(ap, fmt);
328     for (p = fmt; p && *p; p++) {
329         switch (*p) {
330         case 'p':                       /* 32-bit pointer */
331             i = __va_arg(ap, u_int);
332             arg_start = min(arg_start, i);
333             arg_end = max(arg_end, i);
334             flags |= BIOSARGS_FLAG;
335             stack -= 4;
336             break;
337
338         case 'i':                       /* 32-bit integer */
339             i = __va_arg(ap, u_int);
340             stack -= 4;
341             break;
342
343         case 'U':                       /* 16-bit selector */
344             flags |= BIOSUTIL_FLAG;
345             /* FALLTHROUGH */
346         case 'D':                       /* 16-bit selector */
347         case 'C':                       /* 16-bit selector */
348             stack -= 2;
349             break;
350             
351         case 's':                       /* 16-bit integer passed as an int */
352             i = __va_arg(ap, int);
353             stack -= 2;
354             break;
355
356         default:
357             return (EINVAL);
358         }
359     }
360
361     if (flags & BIOSARGS_FLAG) {
362         if (arg_end - arg_start > ctob(16))
363             return (EACCES);
364         args->seg.args.base = arg_start;
365         args->seg.args.limit = 0xffff;
366     }
367
368     args->seg.code32.base = (u_int)&bios16_jmp & PG_FRAME;
369     args->seg.code32.limit = 0xffff;    
370
371     ptd = (pd_entry_t *)rcr3();
372     if ((pd_entry_t)ptd == IdlePTD) {
373         /*
374          * no page table, so create one and install it.
375          */
376         pte = malloc(PAGE_SIZE, M_TEMP, M_WAITOK|M_ZERO);
377         ptd = (pd_entry_t *)((vm_offset_t)ptd + KERNBASE);
378         *ptd = vtophys(pte) | PG_RW | PG_V;
379     } else {
380         /*
381          * this is a user-level page table 
382          */
383         pte = PTmap;
384     }
385     /*
386      * install pointer to page 0.  Flush the tlb for safety.  We don't
387      * migrate between cpus so a local flush is sufficient.
388      */
389     *pte = (vm86pa - PAGE_SIZE) | PG_RW | PG_V; 
390     cpu_invltlb();
391
392     stack_top = stack;
393     __va_start(ap, fmt);
394     for (p = fmt; p && *p; p++) {
395         switch (*p) {
396         case 'p':                       /* 32-bit pointer */
397             i = __va_arg(ap, u_int);
398             *(u_int *)stack = (i - arg_start) |
399                 (GSEL(GBIOSARGS_SEL, SEL_KPL) << 16);
400             stack += 4;
401             break;
402
403         case 'i':                       /* 32-bit integer */
404             i = __va_arg(ap, u_int);
405             *(u_int *)stack = i;
406             stack += 4;
407             break;
408
409         case 'U':                       /* 16-bit selector */
410             *(u_short *)stack = GSEL(GBIOSUTIL_SEL, SEL_KPL);
411             stack += 2;
412             break;
413
414         case 'D':                       /* 16-bit selector */
415             *(u_short *)stack = GSEL(GBIOSDATA_SEL, SEL_KPL);
416             stack += 2;
417             break;
418
419         case 'C':                       /* 16-bit selector */
420             *(u_short *)stack = GSEL(GBIOSCODE16_SEL, SEL_KPL);
421             stack += 2;
422             break;
423
424         case 's':                       /* 16-bit integer passed as an int */
425             i = __va_arg(ap, int);
426             *(u_short *)stack = i;
427             stack += 2;
428             break;
429
430         default:
431             return (EINVAL);
432         }
433     }
434
435     set_bios_selectors(&args->seg, flags);
436     bioscall_vector.vec16.offset = (u_short)args->entry;
437     bioscall_vector.vec16.segment = GSEL(GBIOSCODE16_SEL, SEL_KPL);
438
439     i = bios16_call(&args->r, stack_top);
440     
441     if (pte == PTmap) {
442         *pte = 0;                       /* remove entry */
443     } else {
444         *ptd = 0;                       /* remove page table */
445         free(pte, M_TEMP);              /* ... and free it */
446     }
447     /*
448      * XXX only needs to be invlpg(0) but that doesn't work on the 386 
449      */
450     cpu_invltlb();
451     return (i);
452 }