Version 0.1.2
[seabios.git] / src / post.c
1 // 32bit code to Power On Self Test (POST) a machine.
2 //
3 // Copyright (C) 2008  Kevin O'Connor <kevin@koconnor.net>
4 // Copyright (C) 2002  MandrakeSoft S.A.
5 //
6 // This file may be distributed under the terms of the GNU GPLv3 license.
7
8 #include "ioport.h" // PORT_*
9 #include "../out/rom16.offset.auto.h" // OFFSET_*
10 #include "config.h" // CONFIG_*
11 #include "cmos.h" // CMOS_*
12 #include "util.h" // memset
13 #include "biosvar.h" // struct bios_data_area_s
14
15 #define bda ((struct bios_data_area_s *)0)
16 #define ebda ((struct extended_bios_data_area_s *)(EBDA_SEG<<4))
17 #define ipl ((struct ipl_s *)(IPL_SEG<<4))
18
19 static void
20 init_bda()
21 {
22     memset(bda, 0, sizeof(*bda));
23
24     int i;
25     for (i=0; i<256; i++) {
26         bda->ivecs[i].seg = 0xf000;
27         bda->ivecs[i].offset = OFFSET_dummy_iret_handler;
28     }
29
30     bda->mem_size_kb = BASE_MEM_IN_K;
31 }
32
33 static void
34 init_handlers()
35 {
36     // set vector 0x79 to zero
37     // this is used by 'gardian angel' protection system
38     bda->ivecs[0x79].seg = 0;
39     bda->ivecs[0x79].offset = 0;
40
41     bda->ivecs[0x40].offset = OFFSET_entry_40;
42     bda->ivecs[0x0e].offset = OFFSET_entry_0e;
43     bda->ivecs[0x13].offset = OFFSET_entry_13;
44     bda->ivecs[0x76].offset = OFFSET_entry_76;
45     bda->ivecs[0x17].offset = OFFSET_entry_17;
46     bda->ivecs[0x18].offset = OFFSET_entry_18;
47     bda->ivecs[0x19].offset = OFFSET_entry_19;
48     bda->ivecs[0x1c].offset = OFFSET_entry_1c;
49     bda->ivecs[0x12].offset = OFFSET_entry_12;
50     bda->ivecs[0x11].offset = OFFSET_entry_11;
51     bda->ivecs[0x15].offset = OFFSET_entry_15;
52     bda->ivecs[0x08].offset = OFFSET_entry_08;
53     bda->ivecs[0x09].offset = OFFSET_entry_09;
54     bda->ivecs[0x16].offset = OFFSET_entry_16;
55     bda->ivecs[0x14].offset = OFFSET_entry_14;
56     bda->ivecs[0x1a].offset = OFFSET_entry_1a;
57     bda->ivecs[0x70].offset = OFFSET_entry_70;
58     bda->ivecs[0x74].offset = OFFSET_entry_74;
59     bda->ivecs[0x75].offset = OFFSET_entry_75;
60     bda->ivecs[0x10].offset = OFFSET_entry_10;
61 }
62
63 static void
64 init_ebda()
65 {
66     memset(ebda, 0, sizeof(*ebda));
67     ebda->size = EBDA_SIZE;
68     bda->ebda_seg = EBDA_SEG;
69     bda->ivecs[0x41].seg = EBDA_SEG;
70     bda->ivecs[0x41].offset = offsetof(struct extended_bios_data_area_s, fdpt0);
71     bda->ivecs[0x46].seg = EBDA_SEG;
72     bda->ivecs[0x41].offset = offsetof(struct extended_bios_data_area_s, fdpt1);
73 }
74
75 static void
76 pit_setup()
77 {
78     // timer0: binary count, 16bit count, mode 2
79     outb(0x34, PORT_PIT_MODE);
80     // maximum count of 0000H = 18.2Hz
81     outb(0x0, PORT_PIT_COUNTER0);
82     outb(0x0, PORT_PIT_COUNTER0);
83 }
84
85 //--------------------------------------------------------------------------
86 // keyboard_panic
87 //--------------------------------------------------------------------------
88 static void
89 keyboard_panic(u16 status)
90 {
91   // If you're getting a 993 keyboard panic here,
92   // please see the comment in keyboard_init
93
94   BX_PANIC("Keyboard error:%u\n",status);
95 }
96
97 static void
98 kbd_wait(u8 mask, u8 code)
99 {
100     u16 max = 0xffff;
101     while ( ((inb(PORT_KBD_STATUS) & mask) == 0) && (--max>0) )
102         outb(code, PORT_DIAG);
103     if (!max)
104         keyboard_panic(code);
105 }
106
107 static inline void kbd_flush(u8 code) {
108     kbd_wait(0x02, code);
109 }
110 static inline void kbd_waitdata(u8 code) {
111     kbd_wait(0x01, code);
112 }
113
114 //--------------------------------------------------------------------------
115 // keyboard_init
116 //--------------------------------------------------------------------------
117 // this file is based on LinuxBIOS implementation of keyboard.c
118 // could convert to #asm to gain space
119 static void
120 keyboard_init()
121 {
122     /* ------------------- Flush buffers ------------------------*/
123     /* Wait until buffer is empty */
124     kbd_flush(0x00);
125
126     /* flush incoming keys */
127     u16 max=0x2000;
128     while (--max > 0) {
129         outb(0x00, PORT_DIAG);
130         if (inb(PORT_KBD_STATUS) & 0x01) {
131             inb(PORT_KBD_DATA);
132             max = 0x2000;
133             }
134         }
135
136     // Due to timer issues, and if the IPS setting is > 15000000,
137     // the incoming keys might not be flushed here. That will
138     // cause a panic a few lines below.  See sourceforge bug report :
139     // [ 642031 ] FATAL: Keyboard RESET error:993
140
141     /* ------------------- controller side ----------------------*/
142     /* send cmd = 0xAA, self test 8042 */
143     outb(0xaa, PORT_KBD_STATUS);
144
145     kbd_flush(0x00);
146     kbd_waitdata(0x01);
147
148     /* read self-test result, 0x55 should be returned from 0x60 */
149     if ((inb(PORT_KBD_DATA) != 0x55))
150         keyboard_panic(991);
151
152     /* send cmd = 0xAB, keyboard interface test */
153     outb(0xab, PORT_KBD_STATUS);
154
155     kbd_flush(0x10);
156     kbd_waitdata(0x11);
157
158     /* read keyboard interface test result, */
159     /* 0x00 should be returned form 0x60 */
160     if ((inb(PORT_KBD_DATA) != 0x00))
161         keyboard_panic(992);
162
163     /* Enable Keyboard clock */
164     outb(0xae, PORT_KBD_STATUS);
165     outb(0xa8, PORT_KBD_STATUS);
166
167     /* ------------------- keyboard side ------------------------*/
168     /* reset kerboard and self test  (keyboard side) */
169     outb(0xff, PORT_KBD_DATA);
170
171     kbd_flush(0x20);
172     kbd_waitdata(0x21);
173
174     /* keyboard should return ACK */
175     if ((inb(PORT_KBD_DATA) != 0xfa))
176         keyboard_panic(993);
177
178     kbd_waitdata(0x31);
179
180     if ((inb(PORT_KBD_DATA) != 0xaa))
181         keyboard_panic(994);
182
183     /* Disable keyboard */
184     outb(0xf5, PORT_KBD_DATA);
185
186     kbd_flush(0x40);
187     kbd_waitdata(0x41);
188
189     /* keyboard should return ACK */
190     if ((inb(PORT_KBD_DATA) != 0xfa))
191         keyboard_panic(995);
192
193     /* Write Keyboard Mode */
194     outb(0x60, PORT_KBD_STATUS);
195
196     kbd_flush(0x50);
197
198     /* send cmd: scan code convert, disable mouse, enable IRQ 1 */
199     outb(0x61, PORT_KBD_DATA);
200
201     kbd_flush(0x60);
202
203     /* Enable keyboard */
204     outb(0xf4, PORT_KBD_DATA);
205
206     kbd_flush(0x70);
207     kbd_waitdata(0x71);
208
209     /* keyboard should return ACK */
210     if ((inb(PORT_KBD_DATA) != 0xfa))
211         keyboard_panic(996);
212
213     outb(0x77, PORT_DIAG);
214 }
215
216 static void
217 kbd_setup()
218 {
219     bda->kbd_mode = 0x10;
220     bda->kbd_buf_head = bda->kbd_buf_tail = bda->kbd_buf_start_offset
221         = offsetof(struct bios_data_area_s, kbd_buf) - 0x400;
222     bda->kbd_buf_end_offset
223         = (offsetof(struct bios_data_area_s, kbd_buf[sizeof(bda->kbd_buf)])
224            - 0x400);
225     keyboard_init();
226
227     // mov CMOS Equipment Byte to BDA Equipment Word
228     u16 eqb = bda->equipment_list_flags;
229     bda->equipment_list_flags = (eqb & 0xff00) | inb_cmos(CMOS_EQUIPMENT_INFO);
230 }
231
232 static u16
233 detect_parport(u16 port, u8 timeout, u8 count)
234 {
235     // clear input mode
236     outb(inb(port+2) & 0xdf, port+2);
237
238     outb(0xaa, port);
239     if (inb(port) != 0xaa)
240         // Not present
241         return 0;
242     bda->port_lpt[count] = port;
243     bda->lpt_timeout[count] = timeout;
244     return 1;
245 }
246
247 static void
248 lpt_setup()
249 {
250     u16 count = 0;
251     count += detect_parport(0x378, 0x14, count);
252     count += detect_parport(0x278, 0x14, count);
253
254     // Equipment word bits 14..15 determing # parallel ports
255     u16 eqb = bda->equipment_list_flags;
256     bda->equipment_list_flags = (eqb & 0x3fff) | (count << 14);
257 }
258
259 static u16
260 detect_serial(u16 port, u8 timeout, u8 count)
261 {
262     outb(0x02, port+1);
263     if (inb(port+1) != 0x02)
264         return 0;
265     if (inb(port+2) != 0x02)
266         return 0;
267     outb(0x00, port+1);
268     bda->port_com[count] = port;
269     bda->com_timeout[count] = timeout;
270     return 1;
271 }
272
273 static void
274 serial_setup()
275 {
276     u16 count = 0;
277     count += detect_serial(0x3f8, 0x0a, count);
278     count += detect_serial(0x2f8, 0x0a, count);
279     count += detect_serial(0x3e8, 0x0a, count);
280     count += detect_serial(0x2e8, 0x0a, count);
281
282     // Equipment word bits 9..11 determing # serial ports
283     u16 eqb = bda->equipment_list_flags;
284     bda->equipment_list_flags = (eqb & 0xf1ff) | (count << 9);
285 }
286
287 static u32
288 bcd2bin(u8 val)
289 {
290     return (val & 0xf) + ((val >> 4) * 10);
291 }
292
293 static void
294 timer_setup()
295 {
296     u32 seconds = bcd2bin(inb_cmos(CMOS_RTC_SECONDS));
297     u32 ticks = (seconds * 18206507) / 1000000;
298     u32 minutes = bcd2bin(inb_cmos(CMOS_RTC_MINUTES));
299     ticks += (minutes * 10923904) / 10000;
300     u32 hours = bcd2bin(inb_cmos(CMOS_RTC_HOURS));
301     ticks += (hours * 65543427) / 1000;
302     bda->timer_counter = ticks;
303     bda->timer_rollover = 0;
304 }
305
306 static void
307 pic_setup()
308 {
309     outb(0x11, PORT_PIC1);
310     outb(0x11, PORT_PIC2_DATA);
311     outb(0x08, PORT_PIC1_DATA);
312     outb(0x70, PORT_PIC2_DATA);
313     outb(0x04, PORT_PIC1_DATA);
314     outb(0x02, PORT_PIC2_DATA);
315     outb(0x01, PORT_PIC1_DATA);
316     outb(0x01, PORT_PIC2_DATA);
317     outb(0xb8, PORT_PIC1_DATA);
318     if (CONFIG_PS2_MOUSE)
319         outb(0x8f, PORT_PIC2_DATA);
320     else
321         outb(0x9f, PORT_PIC2_DATA);
322 }
323
324 static void
325 floppy_drive_post()
326 {
327     u8 type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE);
328     u8 out = 0;
329     if (type & 0xf0)
330         out |= 0x07;
331     if (type & 0x0f)
332         out |= 0x70;
333     bda->floppy_harddisk_info = out;
334     outb(0x02, PORT_DMA1_MASK_REG);
335
336     bda->ivecs[0x1E].offset = OFFSET_diskette_param_table2;
337 }
338
339 static void
340 cdemu_init()
341 {
342     // XXX
343     //ebda->cdemu.active = 0;
344 }
345
346 static void
347 ata_init()
348 {
349     // XXX
350 }
351
352 static void
353 ata_detect()
354 {
355     // XXX
356 }
357
358 static void
359 hard_drive_post()
360 {
361     // XXX
362 }
363
364
365 static void
366 init_boot_vectors()
367 {
368     // Clear out the IPL table.
369     memset(ipl, 0, sizeof(*ipl));
370
371     // Floppy drive
372     struct ipl_entry_s *ip = &ipl->table[0];
373     ip->type = IPL_TYPE_FLOPPY;
374     ip++;
375
376     // First HDD
377     ip->type = IPL_TYPE_HARDDISK;
378     ip++;
379
380     // CDROM
381     if (CONFIG_ELTORITO_BOOT) {
382         ip->type = IPL_TYPE_CDROM;
383         ip++;
384     }
385
386     ipl->count = ip - ipl->table;
387     ipl->sequence = 0xffff;
388 }
389
390 static void
391 callrom(u16 seg, u16 offset)
392 {
393     struct bregs br;
394     memset(&br, 0, sizeof(br));
395     br.es = 0xf000;
396     br.di = OFFSET_pnp_string;
397     br.cs = seg;
398     br.ip = offset;
399     call16(&br);
400 }
401
402 static int
403 checksum(u8 *p, u32 len)
404 {
405     u32 i;
406     u8 sum = 0;
407     for (i=0; i<len; i++)
408         sum += p[i];
409     return sum;
410 }
411
412 static void
413 rom_scan()
414 {
415     u8 *p = (u8*)0xc0000;
416     for (; p <= (u8*)0xe0000; p += 2048) {
417         u8 *rom = p;
418         if (*(u16*)rom != 0xaa55)
419             continue;
420         u32 len = rom[2] * 512;
421         if (checksum(rom, len) != 0)
422             continue;
423         p = (u8*)(((u32)p + len) / 2048 * 2048);
424         callrom(PTR_TO_SEG(rom), PTR_TO_OFFSET(rom + 3));
425
426         // Look at the ROM's PnP Expansion header.  Properly, we're supposed
427         // to init all the ROMs and then go back and build an IPL table of
428         // all the bootable devices, but we can get away with one pass.
429         if (rom[0x1a] != '$' || rom[0x1b] != 'P'
430             || rom[0x1c] != 'n' || rom[0x1d] != 'P')
431             continue;
432         // 0x1A is also the offset into the expansion header of...
433         // the Bootstrap Entry Vector, or zero if there is none.
434         u16 entry = *(u16*)&rom[0x1a+0x1a];
435         if (!entry)
436             continue;
437         // Found a device that thinks it can boot the system.  Record
438         // its BEV and product name string.
439
440         if (ipl->count >= ARRAY_SIZE(ipl->table))
441             continue;
442
443         struct ipl_entry_s *ip = &ipl->table[ipl->count];
444         ip->type = IPL_TYPE_BEV;
445         ip->vector = (PTR_TO_SEG(rom) << 16) | entry;
446
447         u16 desc = *(u16*)&rom[0x1a+0x10];
448         if (desc)
449             ip->description = (PTR_TO_SEG(rom) << 16) | desc;
450
451         ipl->count++;
452     }
453 }
454
455 static void
456 status_restart(u8 status)
457 {
458     // XXX
459 #if 0
460     if (status == 0x05)
461         eoi_jmp_post();
462 #endif
463
464     BX_PANIC("Unimplemented shutdown status: %02x\n",(Bit8u)status);
465 }
466
467 static void
468 post()
469 {
470     // first reset the DMA controllers
471     outb(0, PORT_DMA1_MASTER_CLEAR);
472     outb(0, PORT_DMA2_MASTER_CLEAR);
473
474     // then initialize the DMA controllers
475     outb(0xc0, PORT_DMA2_MODE_REG);
476     outb(0x00, PORT_DMA2_MASK_REG);
477
478     // Get and then clear CMOS shutdown status.
479     u8 status = inb_cmos(CMOS_RESET_CODE);
480     outb_cmos(0, CMOS_RESET_CODE);
481
482     if (status != 0x00 && status != 0x09 && status < 0x0d)
483         status_restart(status);
484
485     BX_INFO("Start bios");
486
487     init_bda();
488     init_handlers();
489     init_ebda();
490
491     pit_setup();
492     kbd_setup();
493     lpt_setup();
494     serial_setup();
495     timer_setup();
496     pic_setup();
497     // XXX - need to do pci stuff
498     //pci_setup();
499     init_boot_vectors();
500     rom_scan();
501
502     printf("BIOS - begin\n\n");
503
504     floppy_drive_post();
505     hard_drive_post();
506     if (CONFIG_ATA) {
507         ata_init();
508         ata_detect();
509     }
510     cdemu_init();
511     callrom(0xf000, OFFSET_begin_boot);
512 }
513
514 void VISIBLE
515 _start()
516 {
517     post();
518 }