blob: 898c3ccc9f6c86b52af3ea6b11455e533a872254 [file] [log] [blame]
Kevin O'Connor30853762009-01-17 18:49:20 -05001// Handler for int 0x15 "system" calls
Kevin O'Connorf076a3e2008-02-25 22:25:15 -05002//
3// Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
4// Copyright (C) 2002 MandrakeSoft S.A.
5//
Kevin O'Connorb1b7c2a2009-01-15 20:52:58 -05006// This file may be distributed under the terms of the GNU LGPLv3 license.
Kevin O'Connorf076a3e2008-02-25 22:25:15 -05007
Kevin O'Connor10ad7992009-10-24 11:06:08 -04008#include "util.h" // memcpy_far
Kevin O'Connor2ad37442008-05-06 19:49:01 -04009#include "biosvar.h" // BIOS_CONFIG_TABLE
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050010#include "ioport.h" // inb
Kevin O'Connorc7812932008-06-08 23:08:12 -040011#include "memmap.h" // E820_RAM
Kevin O'Connorf54c1502008-06-14 15:56:16 -040012#include "pic.h" // eoi_pic2
Kevin O'Connor9521e262008-07-04 13:04:29 -040013#include "bregs.h" // struct bregs
Kevin O'Connore2e5f012008-03-08 10:27:39 -050014
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050015// Use PS2 System Control port A to set A20 enable
16static inline u8
17set_a20(u8 cond)
18{
19 // get current setting first
20 u8 newval, oldval = inb(PORT_A20);
21 if (cond)
Kevin O'Connord9a8b2d2008-11-16 09:17:02 -050022 newval = oldval | A20_ENABLE_BIT;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050023 else
Kevin O'Connord9a8b2d2008-11-16 09:17:02 -050024 newval = oldval & ~A20_ENABLE_BIT;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050025 outb(newval, PORT_A20);
26
Kevin O'Connord9a8b2d2008-11-16 09:17:02 -050027 return (oldval & A20_ENABLE_BIT) != 0;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050028}
29
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050030static void
31handle_152400(struct bregs *regs)
32{
33 set_a20(0);
Kevin O'Connor6c781222008-03-09 12:19:23 -040034 set_code_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050035}
36
37static void
38handle_152401(struct bregs *regs)
39{
40 set_a20(1);
Kevin O'Connor6c781222008-03-09 12:19:23 -040041 set_code_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050042}
43
44static void
45handle_152402(struct bregs *regs)
46{
Kevin O'Connord9a8b2d2008-11-16 09:17:02 -050047 regs->al = (inb(PORT_A20) & A20_ENABLE_BIT) != 0;
Kevin O'Connor6c781222008-03-09 12:19:23 -040048 set_code_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050049}
50
51static void
52handle_152403(struct bregs *regs)
53{
54 regs->bx = 3;
Kevin O'Connor6c781222008-03-09 12:19:23 -040055 set_code_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050056}
57
58static void
59handle_1524XX(struct bregs *regs)
60{
Kevin O'Connordfefeb52009-12-13 13:04:17 -050061 set_code_unimplemented(regs, RET_EUNSUPPORTED);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050062}
63
Kevin O'Connoradb6b372008-03-01 13:38:38 -050064static void
65handle_1524(struct bregs *regs)
66{
67 switch (regs->al) {
68 case 0x00: handle_152400(regs); break;
69 case 0x01: handle_152401(regs); break;
70 case 0x02: handle_152402(regs); break;
71 case 0x03: handle_152403(regs); break;
72 default: handle_1524XX(regs); break;
73 }
74}
75
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050076// removable media eject
77static void
78handle_1552(struct bregs *regs)
79{
Kevin O'Connor6c781222008-03-09 12:19:23 -040080 set_code_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050081}
82
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050083static void
84handle_1587(struct bregs *regs)
85{
86 // +++ should probably have descriptor checks
87 // +++ should have exception handlers
88
Kevin O'Connorf076a3e2008-02-25 22:25:15 -050089 u8 prev_a20_enable = set_a20(1); // enable A20 line
90
91 // 128K max of transfer on 386+ ???
92 // source == destination ???
93
94 // ES:SI points to descriptor table
95 // offset use initially comments
96 // ==============================================
97 // 00..07 Unused zeros Null descriptor
98 // 08..0f GDT zeros filled in by BIOS
99 // 10..17 source ssssssss source of data
100 // 18..1f dest dddddddd destination of data
101 // 20..27 CS zeros filled in by BIOS
102 // 28..2f SS zeros filled in by BIOS
103
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500104// check for access rights of source & dest here
105
106 // Initialize GDT descriptor
Kevin O'Connoreaa2e552009-08-10 00:03:04 -0400107 u32 si = regs->si;
108 u64 *gdt_far = (void*)si;
109 u16 gdt_seg = regs->es;
110 u32 loc = (u32)MAKE_FLATPTR(gdt_seg, gdt_far);
111 SET_FARVAR(gdt_seg, gdt_far[1], GDT_DATA | GDT_LIMIT((6*sizeof(u64))-1)
112 | GDT_BASE(loc));
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500113 // Initialize CS descriptor
Kevin O'Connor643062f2010-01-04 20:48:20 -0500114 SET_FARVAR(gdt_seg, gdt_far[4], GDT_CODE | GDT_LIMIT(BUILD_BIOS_SIZE-1)
115 | GDT_BASE(BUILD_BIOS_ADDR));
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500116 // Initialize SS descriptor
Kevin O'Connoreaa2e552009-08-10 00:03:04 -0400117 loc = (u32)MAKE_FLATPTR(GET_SEG(SS), 0);
118 SET_FARVAR(gdt_seg, gdt_far[5], GDT_DATA | GDT_LIMIT(0x0ffff)
119 | GDT_BASE(loc));
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500120
Kevin O'Connor2cdd8b62008-03-09 23:37:04 -0400121 u16 count = regs->cx;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500122 asm volatile(
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500123 // Load new descriptor tables
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500124 " lgdtw %%es:(1<<3)(%%si)\n"
125 " lidtw %%cs:pmode_IDT_info\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500126
Kevin O'Connor3eac0092008-11-16 09:59:32 -0500127 // Enable protected mode
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500128 " movl %%cr0, %%eax\n"
129 " orl $" __stringify(CR0_PE) ", %%eax\n"
130 " movl %%eax, %%cr0\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500131
132 // far jump to flush CPU queue after transition to protected mode
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500133 " ljmpw $(4<<3), $1f\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500134
135 // GDT points to valid descriptor table, now load DS, ES
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500136 "1:movw $(2<<3), %%ax\n" // 2nd descriptor in table, TI=GDT, RPL=00
137 " movw %%ax, %%ds\n"
138 " movw $(3<<3), %%ax\n" // 3rd descriptor in table, TI=GDT, RPL=00
139 " movw %%ax, %%es\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500140
141 // move CX words from DS:SI to ES:DI
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500142 " xorw %%si, %%si\n"
143 " xorw %%di, %%di\n"
144 " rep movsw\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500145
Kevin O'Connorc35e1e52010-03-15 19:58:55 -0400146 // Restore DS and ES segment limits to 0xffff
147 " movw $(5<<3), %%ax\n" // 5th descriptor in table (SS)
148 " movw %%ax, %%ds\n"
149 " movw %%ax, %%es\n"
150
Kevin O'Connor3eac0092008-11-16 09:59:32 -0500151 // Disable protected mode
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500152 " movl %%cr0, %%eax\n"
153 " andl $~" __stringify(CR0_PE) ", %%eax\n"
154 " movl %%eax, %%cr0\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500155
156 // far jump to flush CPU queue after transition to real mode
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500157 " ljmpw $" __stringify(SEG_BIOS) ", $2f\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500158
159 // restore IDT to normal real-mode defaults
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500160 "2:lidtw %%cs:rmode_IDT_info\n"
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500161
Kevin O'Connore20ed9f2008-03-01 14:25:44 -0500162 // Restore %ds (from %ss)
Kevin O'Connor7e6bd3e2010-01-04 21:04:04 -0500163 " movw %%ss, %%ax\n"
164 " movw %%ax, %%ds\n"
Kevin O'Connor2cdd8b62008-03-09 23:37:04 -0400165 : "+c"(count), "+S"(si)
Kevin O'Connored128492008-03-11 11:14:59 -0400166 : : "eax", "di", "cc"); // XXX - also clobbers %es
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500167
168 set_a20(prev_a20_enable);
169
Kevin O'Connor6c781222008-03-09 12:19:23 -0400170 set_code_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500171}
172
173// Get the amount of extended memory (above 1M)
174static void
175handle_1588(struct bregs *regs)
176{
Kevin O'Connore7916362008-12-28 22:03:17 -0500177 u32 rs = GET_GLOBAL(RamSize);
Kevin O'Connor9571ac22008-05-17 22:20:27 -0400178
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500179 // According to Ralf Brown's interrupt the limit should be 15M,
180 // but real machines mostly return max. 63M.
Kevin O'Connor9571ac22008-05-17 22:20:27 -0400181 if (rs > 64*1024*1024)
182 regs->ax = 63 * 1024;
183 else
184 regs->ax = (rs - 1*1024*1024) / 1024;
Kevin O'Connor6c781222008-03-09 12:19:23 -0400185 set_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500186}
187
Kevin O'Connor643062f2010-01-04 20:48:20 -0500188// Switch to protected mode
189static void
190handle_1589(struct bregs *regs)
191{
192 set_a20(1);
193
194 set_pics(regs->bl, regs->bh);
195
196 u64 *gdt_far = (void*)(regs->si + 0);
197 u16 gdt_seg = regs->es;
198 SET_FARVAR(gdt_seg, gdt_far[7], GDT_CODE | GDT_LIMIT(BUILD_BIOS_SIZE-1)
199 | GDT_BASE(BUILD_BIOS_ADDR));
200
201 regs->ds = 3<<3; // 3rd gdt descriptor is %ds
202 regs->es = 4<<3; // 4th gdt descriptor is %es
203 regs->code.seg = 6<<3; // 6th gdt descriptor is %cs
204
205 set_code_success(regs);
206
207 asm volatile(
208 // Load new descriptor tables
209 " lgdtw %%es:(1<<3)(%%si)\n"
210 " lidtw %%es:(2<<3)(%%si)\n"
211
212 // Enable protected mode
213 " movl %%cr0, %%eax\n"
214 " orl $" __stringify(CR0_PE) ", %%eax\n"
215 " movl %%eax, %%cr0\n"
216
217 // far jump to flush CPU queue after transition to protected mode
218 " ljmpw $(7<<3), $1f\n"
219
220 // GDT points to valid descriptor table, now load SS
221 "1:movw $(5<<3), %%ax\n"
222 " movw %%ax, %%ds\n"
223 " movw %%ax, %%ss\n"
224 :
225 : "S"(gdt_far)
226 : "eax", "cc");
227}
228
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500229// Device busy interrupt. Called by Int 16h when no key available
230static void
231handle_1590(struct bregs *regs)
232{
233}
234
235// Interrupt complete. Called by Int 16h when key becomes available
236static void
237handle_1591(struct bregs *regs)
238{
239}
240
Kevin O'Connor38fcbfe2008-02-25 22:30:47 -0500241// keyboard intercept
242static void
243handle_154f(struct bregs *regs)
244{
Kevin O'Connordfefeb52009-12-13 13:04:17 -0500245 set_invalid_silent(regs);
Kevin O'Connor38fcbfe2008-02-25 22:30:47 -0500246}
247
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500248static void
249handle_15c0(struct bregs *regs)
250{
251 regs->es = SEG_BIOS;
Kevin O'Connor117fc212008-04-13 18:17:02 -0400252 regs->bx = (u32)&BIOS_CONFIG_TABLE;
Kevin O'Connor6c781222008-03-09 12:19:23 -0400253 set_code_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500254}
255
256static void
257handle_15c1(struct bregs *regs)
258{
Kevin O'Connor08815372008-12-29 21:16:31 -0500259 regs->es = get_ebda_seg();
Kevin O'Connor6c781222008-03-09 12:19:23 -0400260 set_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500261}
262
263static void
264handle_15e801(struct bregs *regs)
265{
266 // my real system sets ax and bx to 0
267 // this is confirmed by Ralph Brown list
268 // but syslinux v1.48 is known to behave
269 // strangely if ax is set to 0
270 // regs.u.r16.ax = 0;
271 // regs.u.r16.bx = 0;
272
Kevin O'Connore7916362008-12-28 22:03:17 -0500273 u32 rs = GET_GLOBAL(RamSize);
Kevin O'Connor9571ac22008-05-17 22:20:27 -0400274
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500275 // Get the amount of extended memory (above 1M)
Kevin O'Connor9571ac22008-05-17 22:20:27 -0400276 if (rs > 16*1024*1024) {
277 // limit to 15M
278 regs->cx = 15*1024;
279 // Get the amount of extended memory above 16M in 64k blocks
280 regs->dx = (rs - 16*1024*1024) / (64*1024);
281 } else {
282 regs->cx = (rs - 1*1024*1024) / 1024;
283 regs->dx = 0;
284 }
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500285
286 // Set configured memory equal to extended memory
287 regs->ax = regs->cx;
288 regs->bx = regs->dx;
289
Kevin O'Connor6c781222008-03-09 12:19:23 -0400290 set_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500291}
292
Kevin O'Connord995b3d2008-11-08 13:05:27 -0500293// Info on e820 map location and size.
Kevin O'Connor372e0712009-09-09 09:51:31 -0400294struct e820entry e820_list[CONFIG_MAX_E820] VAR16VISIBLE;
295int e820_count VAR16VISIBLE;
Kevin O'Connord995b3d2008-11-08 13:05:27 -0500296
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500297static void
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500298handle_15e820(struct bregs *regs)
299{
Kevin O'Connor15157a32008-12-13 11:10:37 -0500300 int count = GET_GLOBAL(e820_count);
Kevin O'Connorf969a082009-05-06 23:21:13 -0400301 if (regs->edx != 0x534D4150 || regs->bx >= count
302 || regs->ecx < sizeof(e820_list[0])) {
Kevin O'Connordfefeb52009-12-13 13:04:17 -0500303 set_code_invalid(regs, RET_EUNSUPPORTED);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500304 return;
305 }
306
Kevin O'Connor8b267cb2009-01-19 19:25:21 -0500307 memcpy_far(regs->es, (void*)(regs->di+0)
308 , get_global_seg(), &e820_list[regs->bx]
309 , sizeof(e820_list[0]));
Kevin O'Connorc7812932008-06-08 23:08:12 -0400310 if (regs->bx == count-1)
311 regs->ebx = 0;
312 else
313 regs->ebx++;
314 regs->eax = 0x534D4150;
Kevin O'Connordf2c19a2009-01-17 20:07:09 -0500315 regs->ecx = sizeof(e820_list[0]);
Kevin O'Connorc7812932008-06-08 23:08:12 -0400316 set_success(regs);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500317}
318
319static void
320handle_15e8XX(struct bregs *regs)
321{
Kevin O'Connordfefeb52009-12-13 13:04:17 -0500322 set_code_unimplemented(regs, RET_EUNSUPPORTED);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500323}
324
325static void
Kevin O'Connoradb6b372008-03-01 13:38:38 -0500326handle_15e8(struct bregs *regs)
327{
328 switch (regs->al) {
329 case 0x01: handle_15e801(regs); break;
330 case 0x20: handle_15e820(regs); break;
331 default: handle_15e8XX(regs); break;
332 }
333}
334
335static void
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500336handle_15XX(struct bregs *regs)
337{
Kevin O'Connordfefeb52009-12-13 13:04:17 -0500338 set_code_unimplemented(regs, RET_EUNSUPPORTED);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500339}
340
341// INT 15h System Services Entry Point
Kevin O'Connor19786762008-03-05 21:09:59 -0500342void VISIBLE16
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500343handle_15(struct bregs *regs)
344{
Kevin O'Connor15c1f222008-06-12 22:59:43 -0400345 debug_enter(regs, DEBUG_HDL_15);
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500346 switch (regs->ah) {
Kevin O'Connoradb6b372008-03-01 13:38:38 -0500347 case 0x24: handle_1524(regs); break;
Kevin O'Connor38fcbfe2008-02-25 22:30:47 -0500348 case 0x4f: handle_154f(regs); break;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500349 case 0x52: handle_1552(regs); break;
Kevin O'Connorbdce35f2008-02-26 21:33:14 -0500350 case 0x53: handle_1553(regs); break;
Kevin O'Connorcbffa8e2008-08-17 11:11:07 -0400351 case 0x5f: handle_155f(regs); break;
Kevin O'Connorbdce35f2008-02-26 21:33:14 -0500352 case 0x83: handle_1583(regs); break;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500353 case 0x86: handle_1586(regs); break;
354 case 0x87: handle_1587(regs); break;
355 case 0x88: handle_1588(regs); break;
Kevin O'Connor643062f2010-01-04 20:48:20 -0500356 case 0x89: handle_1589(regs); break;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500357 case 0x90: handle_1590(regs); break;
358 case 0x91: handle_1591(regs); break;
359 case 0xc0: handle_15c0(regs); break;
360 case 0xc1: handle_15c1(regs); break;
361 case 0xc2: handle_15c2(regs); break;
Kevin O'Connoradb6b372008-03-01 13:38:38 -0500362 case 0xe8: handle_15e8(regs); break;
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500363 default: handle_15XX(regs); break;
364 }
Kevin O'Connorf076a3e2008-02-25 22:25:15 -0500365}