977d44f3d1b8e416f6dec3b5551dcca5151129ab
[people/lynusvaz/gpxe.git] / src / arch / i386 / interface / syslinux / comboot_call.c
1 /*
2  * Copyright (C) 2008 Daniel Verkamp <daniel@drv.nu>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 /**
20  * @file SYSLINUX COMBOOT API
21  *
22  */
23
24 #include <errno.h>
25 #include <realmode.h>
26 #include <biosint.h>
27 #include <console.h>
28 #include <stdlib.h>
29 #include <comboot.h>
30 #include <bzimage.h>
31 #include <pxe_call.h>
32 #include <setjmp.h>
33 #include <string.h>
34 #include <gpxe/posix_io.h>
35 #include <gpxe/process.h>
36 #include <gpxe/serial.h>
37 #include <gpxe/init.h>
38
39 /** The "SYSLINUX" version string */
40 static char __data16_array ( syslinux_version, [] ) = "gPXE " VERSION;
41 #define syslinux_version __use_data16 ( syslinux_version )
42
43 /** The "SYSLINUX" copyright string */
44 static char __data16_array ( syslinux_copyright, [] ) = "http://etherboot.org";
45 #define syslinux_copyright __use_data16 ( syslinux_copyright )
46
47 static char __data16_array ( syslinux_configuration_file, [] ) = "";
48 #define syslinux_configuration_file __use_data16 ( syslinux_configuration_file )
49
50 /** Feature flags */
51 static uint8_t __data16 ( comboot_feature_flags ) = COMBOOT_FEATURE_IDLE_LOOP;
52 #define comboot_feature_flags __use_data16 ( comboot_feature_flags )
53
54 static struct segoff __text16 ( int20_vector );
55 #define int20_vector __use_text16 ( int20_vector )
56
57 static struct segoff __text16 ( int21_vector );
58 #define int21_vector __use_text16 ( int21_vector )
59
60 static struct segoff __text16 ( int22_vector );
61 #define int22_vector __use_text16 ( int22_vector )
62
63 extern void int20_wrapper ( void );
64 extern void int21_wrapper ( void );
65 extern void int22_wrapper ( void );
66
67 /* setjmp/longjmp context buffer used to return after loading an image */
68 jmp_buf comboot_return;
69
70 /* Command line to execute when returning via comboot_return 
71  * with COMBOOT_RETURN_RUN_KERNEL
72  */
73 char *comboot_kernel_cmdline;
74
75 /* Mode flags set by INT 22h AX=0017h */
76 static uint16_t comboot_graphics_mode = 0;
77
78
79 /**
80  * Print a string with a particular terminator
81  */
82 static void print_user_string ( unsigned int segment, unsigned int offset, char terminator ) {
83         int i = 0;
84         char c;
85         userptr_t str = real_to_user ( segment, offset );
86         for ( ; ; ) {
87                 copy_from_user ( &c, str, i, 1 );
88                 if ( c == terminator ) break;
89                 putchar ( c );
90                 i++;
91         }
92 }
93
94
95 /**
96  * Perform a series of memory copies from a list in low memory
97  */
98 static void shuffle ( unsigned int list_segment, unsigned int list_offset, unsigned int count )
99 {
100         comboot_shuffle_descriptor shuf[COMBOOT_MAX_SHUFFLE_DESCRIPTORS];
101         unsigned int i;
102
103         /* Copy shuffle descriptor list so it doesn't get overwritten */
104         copy_from_user ( shuf, real_to_user ( list_segment, list_offset ), 0,
105                          count * sizeof( comboot_shuffle_descriptor ) );
106
107         /* Do the copies */
108         for ( i = 0; i < count; i++ ) {
109                 userptr_t src_u = phys_to_user ( shuf[ i ].src );
110                 userptr_t dest_u = phys_to_user ( shuf[ i ].dest );
111
112                 if ( shuf[ i ].src == 0xFFFFFFFF ) {
113                         /* Fill with 0 instead of copying */
114                         memset_user ( dest_u, 0, 0, shuf[ i ].len );
115                 } else if ( shuf[ i ].dest == 0xFFFFFFFF ) {
116                         /* Copy new list of descriptors */
117                         count = shuf[ i ].len / sizeof( comboot_shuffle_descriptor );
118                         assert ( count <= COMBOOT_MAX_SHUFFLE_DESCRIPTORS );
119                         copy_from_user ( shuf, src_u, 0, shuf[ i ].len );
120                         i = -1;
121                 } else {
122                         /* Regular copy */
123                         memmove_user ( dest_u, 0, src_u, 0, shuf[ i ].len );
124                 }
125         }
126 }
127
128
129 /**
130  * Set default text mode
131  */
132 void comboot_force_text_mode ( void ) {
133         if ( comboot_graphics_mode & COMBOOT_VIDEO_VESA ) {
134                 /* Set VGA mode 3 via VESA VBE mode set */
135                 __asm__ __volatile__ (
136                         REAL_CODE (
137                                 "mov $0x4F02, %%ax\n\t"
138                                 "mov $0x03, %%bx\n\t"
139                                 "int $0x10\n\t"
140                         )
141                 : : );
142         } else if ( comboot_graphics_mode & COMBOOT_VIDEO_GRAPHICS ) {
143                 /* Set VGA mode 3 via standard VGA mode set */
144                 __asm__ __volatile__ (
145                         REAL_CODE (
146                                 "mov $0x03, %%ax\n\t"
147                                 "int $0x10\n\t"
148                         )
149                 : : );
150         }
151
152         comboot_graphics_mode = 0;
153 }
154
155
156 /**
157  * Run the kernel specified in comboot_kernel_cmdline
158  */
159 void comboot_run_kernel ( )
160 {
161         char *initrd;
162
163         comboot_force_text_mode ( );
164
165         DBG ( "COMBOOT: executing image '%s'\n", comboot_kernel_cmdline );
166
167         /* Find initrd= parameter, if any */
168         if ( ( initrd = strstr ( comboot_kernel_cmdline, "initrd=" ) ) ) {
169                 char old_char = '\0';
170                 char *initrd_end = strchr( initrd, ' ' );
171
172                 /* Replace space after end of parameter
173                  * with a nul terminator if this is not
174                  * the last parameter
175                  */
176                 if ( initrd_end ) {
177                         old_char = *initrd_end;
178                         *initrd_end = '\0';
179                 }
180
181                 /* Replace = with space to get 'initrd filename'
182                  * command suitable for system()
183                  */
184                 initrd[6] = ' ';
185
186                 DBG( "COMBOOT: loading initrd '%s'\n", initrd );
187
188                 system ( initrd );
189
190                 /* Restore space after parameter */
191                 if ( initrd_end ) {
192                         *initrd_end = old_char;
193                 }
194
195                 /* Restore = */
196                 initrd[6] = '=';
197         }
198
199         /* Load kernel */
200         DBG ( "COMBOOT: loading kernel '%s'\n", comboot_kernel_cmdline );
201         system ( comboot_kernel_cmdline );
202
203         free ( comboot_kernel_cmdline );
204
205         /* Boot */
206         system ( "boot" );
207
208         DBG ( "COMBOOT: back from executing command\n" );
209 }
210
211
212 /**
213  * Terminate program interrupt handler
214  */
215 static __asmcall void int20 ( struct i386_all_regs *ix86 __unused ) {
216         longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
217 }
218
219
220 /**
221  * DOS-compatible API
222  */
223 static __asmcall void int21 ( struct i386_all_regs *ix86 ) {
224         ix86->flags |= CF;
225
226         switch ( ix86->regs.ah ) {
227         case 0x00:
228         case 0x4C: /* Terminate program */
229                 longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
230                 break;
231
232         case 0x01: /* Get Key with Echo */
233         case 0x08: /* Get Key without Echo */
234                 /* TODO: handle extended characters? */
235                 ix86->regs.al = getchar( );
236
237                 /* Enter */
238                 if ( ix86->regs.al == 0x0A )
239                         ix86->regs.al = 0x0D;
240
241                 if ( ix86->regs.ah == 0x01 )
242                         putchar ( ix86->regs.al );
243
244                 ix86->flags &= ~CF;
245                 break;
246
247         case 0x02: /* Write Character */
248                 putchar ( ix86->regs.dl );
249                 ix86->flags &= ~CF;
250                 break;
251
252         case 0x04: /* Write Character to Serial Port */
253                 serial_putc ( ix86->regs.dl );
254                 ix86->flags &= ~CF;
255                 break;
256
257         case 0x09: /* Write DOS String to Console */
258                 print_user_string ( ix86->segs.ds, ix86->regs.dx, '$' );
259                 ix86->flags &= ~CF;
260                 break;
261
262         case 0x0B: /* Check Keyboard */
263                 if ( iskey() )
264                         ix86->regs.al = 0xFF;
265                 else
266                         ix86->regs.al = 0x00;
267
268                 ix86->flags &= ~CF;
269                 break;
270
271         case 0x30: /* Check DOS Version */
272                 /* Bottom halves all 0; top halves spell "SYSLINUX" */
273                 ix86->regs.eax = 0x59530000;
274                 ix86->regs.ebx = 0x4C530000;
275                 ix86->regs.ecx = 0x4E490000;
276                 ix86->regs.edx = 0x58550000;
277                 ix86->flags &= ~CF;
278                 break;
279
280         default:
281                 DBG ( "COMBOOT unknown int21 function %02x\n", ix86->regs.ah );
282                 break;
283         }
284 }
285
286
287 /**
288  * SYSLINUX API
289  */
290 static __asmcall void int22 ( struct i386_all_regs *ix86 ) {
291         ix86->flags |= CF;
292
293         switch ( ix86->regs.ax ) {
294         case 0x0001: /* Get Version */
295
296                 /* Number of INT 22h API functions available */
297                 ix86->regs.ax = 0x0018;
298
299                 /* SYSLINUX version number */
300                 ix86->regs.ch = 0; /* major */
301                 ix86->regs.cl = 0; /* minor */
302
303                 /* SYSLINUX derivative ID */
304                 ix86->regs.dl = BZI_LOADER_TYPE_GPXE;
305
306                 /* SYSLINUX version and copyright strings */
307                 ix86->segs.es = rm_ds;
308                 ix86->regs.si = ( ( unsigned ) __from_data16 ( syslinux_version ) );
309                 ix86->regs.di = ( ( unsigned ) __from_data16 ( syslinux_copyright ) );
310
311                 ix86->flags &= ~CF;
312                 break;
313
314         case 0x0002: /* Write String */
315                 print_user_string ( ix86->segs.es, ix86->regs.bx, '\0' );
316                 ix86->flags &= ~CF;
317                 break;
318
319         case 0x0003: /* Run command */
320                 {
321                         userptr_t cmd_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
322                         int len = strlen_user ( cmd_u, 0 );
323                         char cmd[len + 1];
324                         copy_from_user ( cmd, cmd_u, 0, len + 1 );
325                         DBG ( "COMBOOT: executing command '%s'\n", cmd );
326
327                         comboot_kernel_cmdline = strdup ( cmd );
328
329                         DBG ( "COMBOOT: returning to run image...\n" );
330                         longjmp ( comboot_return, COMBOOT_RETURN_RUN_KERNEL );
331                 }
332                 break;
333
334         case 0x0004: /* Run default command */
335                 /* FIXME: just exit for now */
336                 longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
337                 break;
338
339         case 0x0005: /* Force text mode */
340                 comboot_force_text_mode ( );
341                 ix86->flags &= ~CF;
342                 break;
343
344         case 0x0006: /* Open file */
345                 {
346                         int fd;
347                         userptr_t file_u = real_to_user ( ix86->segs.es, ix86->regs.si );
348                         int len = strlen_user ( file_u, 0 );
349                         char file[len + 1];
350
351                         copy_from_user ( file, file_u, 0, len + 1 );
352
353                         if ( file[0] == '\0' ) {
354                                 DBG ( "COMBOOT: attempted open with empty file name\n" );
355                                 break;
356                         }
357
358                         DBG ( "COMBOOT: opening file '%s'\n", file );
359
360                         fd = open ( file );
361
362                         if ( fd < 0 ) {
363                                 DBG ( "COMBOOT: error opening file %s\n", file );
364                                 break;
365                         }
366
367                         /* This relies on the fact that a gPXE POSIX fd will
368                          * always fit in 16 bits.
369                          */
370 #if (POSIX_FD_MAX > 65535)
371 #error POSIX_FD_MAX too large
372 #endif
373                         ix86->regs.si = (uint16_t) fd;
374
375                         ix86->regs.cx = COMBOOT_FILE_BLOCKSZ;
376                         ix86->regs.eax = fsize ( fd );
377                         ix86->flags &= ~CF;
378                 }
379                 break;
380
381         case 0x0007: /* Read file */
382                 {
383                         int fd = ix86->regs.si;
384                         int len = ix86->regs.cx * COMBOOT_FILE_BLOCKSZ;
385                         int rc;
386                         fd_set fds;
387                         userptr_t buf = real_to_user ( ix86->segs.es, ix86->regs.bx );
388
389                         /* Wait for data ready to read */
390                         FD_ZERO ( &fds );
391                         FD_SET ( fd, &fds );
392
393                         select ( &fds, 1 );
394
395                         rc = read_user ( fd, buf, 0, len );
396                         if ( rc < 0 ) {
397                                 DBG ( "COMBOOT: read failed\n" );
398                                 ix86->regs.si = 0;
399                                 break;
400                         }
401
402                         ix86->regs.ecx = rc;
403                         ix86->flags &= ~CF;
404                 }
405                 break;
406
407         case 0x0008: /* Close file */
408                 {
409                         int fd = ix86->regs.si;
410                         close ( fd );
411                         ix86->flags &= ~CF;
412                 }
413                 break;
414
415         case 0x0009: /* Call PXE Stack */
416                 pxe_api_call ( ix86 );
417                 ix86->flags &= ~CF;
418                 break;
419
420         case 0x000A: /* Get Derivative-Specific Information */
421
422                 /* gPXE has its own derivative ID, so there is no defined
423                  * output here; just return AL for now */
424                 ix86->regs.al = BZI_LOADER_TYPE_GPXE;
425                 ix86->flags &= ~CF;
426                 break;
427
428         case 0x000B: /* Get Serial Console Configuration */
429                 /* FIXME: stub */
430                 ix86->regs.dx = 0;
431                 ix86->flags &= ~CF;
432                 break;
433
434         case 0x000E: /* Get configuration file name */
435                 /* FIXME: stub */
436                 ix86->segs.es = rm_ds;
437                 ix86->regs.bx = ( ( unsigned ) __from_data16 ( syslinux_configuration_file ) );
438                 ix86->flags &= ~CF;
439                 break;
440
441         case 0x000F: /* Get IPAPPEND strings */
442                 /* FIXME: stub */
443                 ix86->regs.cx = 0;
444                 ix86->segs.es = 0;
445                 ix86->regs.bx = 0;
446                 ix86->flags &= ~CF;
447                 break;
448
449         case 0x0010: /* Resolve hostname */
450                 {
451                         userptr_t hostname_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
452                         int len = strlen_user ( hostname_u, 0 );
453                         char hostname[len];
454                         struct in_addr addr;
455
456                         copy_from_user ( hostname, hostname_u, 0, len + 1 );
457                         
458                         /* TODO:
459                          * "If the hostname does not contain a dot (.), the
460                          * local domain name is automatically appended."
461                          */
462
463                         comboot_resolv ( hostname, &addr );
464
465                         ix86->regs.eax = addr.s_addr;
466                         ix86->flags &= ~CF;
467                 }
468                 break;
469
470         case 0x0011: /* Maximum number of shuffle descriptors */
471                 ix86->regs.cx = COMBOOT_MAX_SHUFFLE_DESCRIPTORS;
472                 ix86->flags &= ~CF;
473                 break;
474
475         case 0x0012: /* Cleanup, shuffle and boot */
476                 if ( ix86->regs.cx > COMBOOT_MAX_SHUFFLE_DESCRIPTORS )
477                         break;
478
479                 /* Perform final cleanup */
480                 shutdown ( SHUTDOWN_BOOT );
481
482                 /* Perform sequence of copies */
483                 shuffle ( ix86->segs.es, ix86->regs.di, ix86->regs.cx );
484
485                 /* Jump to real-mode entry point */
486                 __asm__ __volatile__ (
487                         REAL_CODE ( 
488                                 "pushw %0\n\t"
489                                 "popw %%ds\n\t"
490                                 "pushl %1\n\t"
491                                 "lret\n\t"
492                         )
493                         :
494                         : "r" ( ix86->segs.ds ),
495                           "r" ( ix86->regs.ebp ),
496                           "d" ( ix86->regs.ebx ),
497                           "S" ( ix86->regs.esi ) );
498
499                 assert ( 0 ); /* Execution should never reach this point */
500
501                 break;
502
503         case 0x0013: /* Idle loop call */
504                 step ( );
505                 ix86->flags &= ~CF;
506                 break;
507
508         case 0x0015: /* Get feature flags */
509                 ix86->segs.es = rm_ds;
510                 ix86->regs.bx = ( ( unsigned ) __from_data16 ( &comboot_feature_flags ) );
511                 ix86->regs.cx = 1; /* Number of feature flag bytes */
512                 ix86->flags &= ~CF;
513                 break;
514
515         case 0x0016: /* Run kernel image */
516                 {
517                         userptr_t file_u = real_to_user ( ix86->segs.ds, ix86->regs.si );
518                         userptr_t cmd_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
519                         int file_len = strlen_user ( file_u, 0 );
520                         int cmd_len = strlen_user ( cmd_u, 0 );
521                         char file[file_len + 1 + cmd_len + 7 + 1];
522                         char cmd[cmd_len + 1];
523
524                         memcpy( file, "kernel ", 7 );
525                         copy_from_user ( file + 7, file_u, 0, file_len + 1 );
526                         copy_from_user ( cmd, cmd_u, 0, cmd_len + 1 );
527                         strcat ( file, " " );
528                         strcat ( file, cmd );
529
530                         DBG ( "COMBOOT: run kernel image '%s'\n", file );
531
532                         comboot_kernel_cmdline = strdup ( file );                       
533
534                         DBG ( "COMBOOT: returning to run image...\n" );
535                         longjmp ( comboot_return, COMBOOT_RETURN_RUN_KERNEL );
536                 }
537                 break;
538
539         case 0x0017: /* Report video mode change */
540                 comboot_graphics_mode = ix86->regs.bx;
541                 ix86->flags &= ~CF;
542                 break;
543
544         case 0x0018: /* Query custom font */
545                 /* FIXME: stub */
546                 ix86->regs.al = 0;
547                 ix86->segs.es = 0;
548                 ix86->regs.bx = 0;
549                 ix86->flags &= ~CF;
550                 break;
551
552         default:
553                 DBG ( "COMBOOT unknown int22 function %04x\n", ix86->regs.ax );
554                 break;
555         }
556 }
557
558 /**
559  * Hook BIOS interrupts related to COMBOOT API (INT 20h, 21h, 22h)
560  */
561 void hook_comboot_interrupts ( ) {
562
563         __asm__ __volatile__ (
564                 TEXT16_CODE ( "\nint20_wrapper:\n\t"
565                               "pushl %0\n\t"
566                               "pushw %%cs\n\t"
567                               "call prot_call\n\t"
568                               "addw $4, %%sp\n\t"
569                               "iret\n\t" )
570                           : : "i" ( int20 ) );
571
572         hook_bios_interrupt ( 0x20, ( unsigned int ) int20_wrapper,
573                                       &int20_vector );
574
575         __asm__ __volatile__ (
576                 TEXT16_CODE ( "\nint21_wrapper:\n\t"
577                               "pushl %0\n\t"
578                               "pushw %%cs\n\t"
579                               "call prot_call\n\t"
580                               "addw $4, %%sp\n\t"
581                               "iret\n\t" )
582                           : : "i" ( int21 ) );
583
584         hook_bios_interrupt ( 0x21, ( unsigned int ) int21_wrapper,
585                               &int21_vector );
586
587         __asm__  __volatile__ (
588                 TEXT16_CODE ( "\nint22_wrapper:\n\t"
589                               "pushl %0\n\t"
590                               "pushw %%cs\n\t"
591                               "call prot_call\n\t"
592                               "addw $4, %%sp\n\t"
593                               "iret\n\t" )
594                           : : "i" ( int22) );
595
596         hook_bios_interrupt ( 0x22, ( unsigned int ) int22_wrapper,
597                               &int22_vector );
598 }