More hacking.
[mknbi.git] / nfl.c
1 #include        "stddef.h"
2 #include        "string.h"
3 #include        "linux-asm-io.h"
4 #include        "string.h"
5 #include        "etherboot.h"
6 #include        "elf_boot.h"
7 #include        "nfl.h"
8
9 /*
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2, or (at
13  * your option) any later version.
14  */
15
16 /*
17
18 This is an example program which shows how the extension routine
19 feature in Etherboot 5.0 works.
20
21 This program presents a list of valid boot images from a data segment
22 that is loaded into another area of memory, and prompts the user for a
23 number, and modifies the bootp record to the filename to be loaded.  You
24 can make the menu program as elaborate as you like, the sky is the
25 limit.
26
27 Ideally, there should be a menu generation program that takes a
28 high-level description of menus and valid inputs and creates a data
29 file to be loaded to the data area. The menu program should agree with
30 the menu generator on the layout of the data area.
31
32 This program is linked to run at 0x60000, and expects to find config
33 data at 0x70000. This means the code can be up to 64kB long.
34
35 When the program starts it receives 3 parameters from Etherboot:
36
37 Pointer to ebinfo structure
38 Pointer to image header structure (either a tagged or ELF image header)
39 Pointer to bootp/DHCP reply obtained by Etherboot from bootpd or DHCPD
40
41 Etherboot expects this program to return an int. The values have these
42 meanings:
43
44 <0      Do not use
45 0       Same as 1, for implementation reasons
46 1       Redo tftp with possibly modified bootp record
47 2       Redo bootp and tftp
48 255     Exit Etherboot
49
50 Observe that this program causes Etherboot to load a different program
51 next by modifying the contents of the filename field in the bootp record
52 and then returning 1. It can also send parameters to the next program by
53 modifying tag 129 in the bootp record. This is how the menu system
54 works.
55
56 The data segment that this particular program expects is of the form
57
58         choice 1\nchoice 2\nchoice 3\n\0
59
60 where the \n are newlines and the \0 the teminating zero byte. Therefore
61 you can create this file with a Unix text editor (but see next
62 paragraph). choice 1, etc are the pathnames or filenames of the next
63 file to load by TFTP. If the string starts with / then it's assumed to
64 be a pathname and the whole of the bootp filename area is replaced by
65 it, otherwise just the filename portion of the pathname, i.e. the same
66 directory as the menu file is assumed.
67
68 This program also illustrates the use of a timeout to select a default
69 item by using currticks() to obtain the value of the BIOS clock and
70 console_ischar to determine if a character has been typed at the
71 keyboard.
72
73 Commentary: This program is just to illustrate a very simple menu
74 system.  There are known bugs:
75
76 mknbi-menu/mkelf-menu does not add the ending NUL byte, but this is
77 present due to the NUL fill to the next block boundary. If the size of
78 the data goes exactly up to a block boundary, then it is possible there
79 will be a spurious final item that goes up the next NUL byte in memory.
80
81 Another bug is that there is no overflow checking when writing into
82 bootp->bp_file.
83
84 Yet another bug is that there is no facility to correct input entry on
85 lines. getline() should be smarter.
86
87 */
88
89 /*
90
91 Memory layout assumed by mknbi and this program
92
93 0x60000-0x6FFFF    64 kB        Menu program
94 0x70000-0x7FFFF    64 kB        Menu data (initial)
95
96 */
97
98 #define NFL_VERSION     "0.56"
99
100 #define TIMEOUT         10                      /* seconds */
101 #define MENU_DATA       ((char *)0x70000)
102 #define MENU_COLS       75
103 #define MENU_ROWS       14
104 #define MAX_ENTRIES     100
105
106 static  int dbg_count1=0;
107
108 static char *menu_entries[MAX_ENTRIES];
109 static unsigned short num_entries;
110 static unsigned short cur_entry;
111 static unsigned short top_entry;
112
113 static int timeout = 0;
114 /* Color settings.  */
115 static int color_fg = 7;
116 static int color_bg = 4;
117 static int number_entries = 0;
118
119 /* Default Menu Size */
120 static unsigned short menu_ulx  = 1;
121 static unsigned short menu_uly  = 1;
122 static unsigned short menu_rows = 0x0800;
123 static unsigned short menu_cols = 0x0800;
124
125 /* Terminal types.  */
126 static int terminal = TERMINAL_CONSOLE;
127
128 /* Defined in ANSI.C */
129 extern unsigned short rows, columns, attr;
130 extern void ansi_putc(unsigned int);
131 extern void ansi_reset(void);
132 extern void enable_cursor(int);
133
134 extern void printf(const char *, ...);
135 extern void sprintf(char *, const char *, ...);
136 extern char *strcat(char *, const char *);
137 extern char *strncat(char *, const char *, unsigned int);
138
139 extern int console_getc(void);
140 extern int console_ischar(void);
141 extern unsigned long currticks(void);
142
143 /*
144 --------------------------------------------------------------------------*/
145 void putchar(int c)
146 {
147         if (c == '\n')
148                 ansi_putc('\r');
149         ansi_putc(c);
150 }
151
152 /*
153 --------------------------------------------------------------------------*/
154 int getchar(void)
155 {
156         int     c;
157
158         c = console_getc();
159         if (c == '\r')
160                 c = '\n';
161         return (c);
162 }
163
164 /* Wait for a keypress and return its code.
165 --------------------------------------------------------------------------*/
166 int getkey (void)
167 {
168   int c = -1;
169   
170   if ((terminal & TERMINAL_CONSOLE)
171 #ifdef SUPPORT_HERCULES
172       || (terminal & TERMINAL_HERCULES)
173 #endif /* SUPPORT_HERCULES */
174       )
175     c = console_getkey ();
176 #ifdef SUPPORT_SERIAL
177   else if (terminal & TERMINAL_SERIAL)
178     c = serial_getkey ();
179 #endif /* SUPPORT_SERIAL */
180
181   return c;
182 }
183
184 /* Check if a key code is available.
185 --------------------------------------------------------------------------*/
186 int checkkey (void)
187 {
188   int c = -1;
189
190   if ((terminal & TERMINAL_CONSOLE)
191 #ifdef SUPPORT_HERCULES
192       || (terminal & TERMINAL_HERCULES)
193 #endif /* SUPPORT_HERCULES */
194       )
195     c = console_checkkey ();
196   
197 #ifdef SUPPORT_SERIAL
198   if (terminal & TERMINAL_SERIAL)
199     c = serial_checkkey ();
200 #endif /* SUPPORT_SERIAL */
201
202   return c;
203 }
204
205 /* Translate a special key to a common ascii code.
206 --------------------------------------------------------------------------*/
207 int translate_keycode (int c)
208 {
209 #if 0 //def SUPPORT_SERIAL
210         if (terminal & TERMINAL_SERIAL)
211         {
212                 /*
213                 In a serial terminal, things are complicated, because several
214                 key codes start from the character ESC, while we want to accept
215                 ESC itself.
216                 */
217                 if (c == '\e')
218                 {
219                         int start;
220
221                         /* Get current time.  */
222                         start = currticks ();
223
224                         while (checkkey () == -1)
225                         {
226                                 /*
227                                 Wait for a next character, at least for 0.1 sec
228                                 (18.2 ticks/sec).
229                                 */
230                                 int now;
231               
232                                 now = currticks ();
233                                 if (now - start >= 2)
234                                         return c;
235                         }
236
237                         c = getkey ();
238                         if (c == '[')
239                         {
240                                 int c1, c2;
241
242                                 /* To filter illegal states.  */
243                                 c = 0;
244                                 c1 = getkey ();
245                                 switch (c1)
246                                 {
247                                         case 'A':       /* KEY_UP */
248                                                 c = 16;
249                                                 break;
250                                         case 'B':       /* KEY_DOWN */
251                                                 c = 14;
252                                                 break;
253                                         case 'C':       /* KEY_RIGHT */
254                                                 c = 6;
255                                                 break;
256                                         case 'D':       /* KEY_LEFT */
257                                                 c = 2;
258                                                 break;
259                                         case 'F':       /* End */
260                                                 c = 5;
261                                                 break;
262                                         case 'H':       /* Home */
263                                                 c = 1;
264                                                 break;
265                                         case '1':
266                                                 c2 = getkey ();
267                                                 if (c2 == '~')
268                                                 {
269                                                         /* One of control keys (pos1,....).  */
270                                                         c = 1;
271                                                 }
272                                                 break;
273                                         case '3':
274                                                 c2 = getkey ();
275                                                 if (c2 == '~')
276                                                 {
277                                                         /* One of control keys (del,....).  */
278                                                         c = 4;
279                                                 }
280                                                 break;
281                                         case '4':       /* Del */
282                                                 c = 4;
283                                                 break;
284                                 }
285                         }
286                 }
287         }
288         else
289 # endif /* SUPPORT_SERIAL */
290         {
291                 switch (c)
292                 {
293                         case KEY_LEFT:
294                                 c = 2;
295                                 break;
296                         case KEY_RIGHT:
297                                 c = 6;
298                                 break;
299                         case KEY_UP:
300                                 c = 16;
301                                 break;
302                         case KEY_DOWN:
303                                 c = 14;
304                                 break;
305                         case KEY_HOME:
306                                 c = 1;
307                                 break;
308                         case KEY_END:
309                                 c = 5;
310                                 break;
311                         case KEY_DC:
312                                 c = 4;
313                                 break;
314                         case KEY_BACKSPACE:
315                                 c = 8;
316                                 break;
317                 }
318         }
319   
320         return ASCII_CHAR (c);
321 }
322
323
324 /* 
325 Get a line, ignore characters after array limit reached. Echo the character 
326 if the flag says so.
327 --------------------------------------------------------------------------*/
328 int getline(char *line, int length, int echo)
329 {
330         int     c;
331         char    *p = line;
332
333         while ((c = getchar()) != '\n') {
334                 if (p < &line[length-1]) {      /* within array? */
335                         if (echo)
336                                 ansi_putc(c);
337                         *p++ = c;
338                 }
339         }
340         *p++ = '\0';
341         if (echo)
342                 ansi_putc('\n');
343         return (line - p);
344 }
345
346 /* Build menu entries array starting at index one for clarity...
347 --------------------------------------------------------------------------*/
348 int scan_entries(void)
349 {
350         int     i = 0, first_entry_char = 1;
351         char    *p = MENU_DATA;
352
353         if (p == '\0')
354                 return( 0 );
355
356         do {
357                 /* Skip leading white-space */
358                 while ((*p == '\b') || (*p == '\t'))
359                         p++;
360                 menu_entries[i+1] = p;
361                 while ((*p != '\0') && (*p != '\n')) 
362                 {
363                         /* Point to next character */
364                         p++;
365                         /* At least one non-blank character, index to next entry */
366                         if (first_entry_char)
367                         {
368                                 first_entry_char = 0;
369                                 i++;
370                         }
371                 }
372                 if (*p == '\0')
373                         break;
374                 if (*p == '\n') {
375                         /* Null-terminate this entry string */
376                         *p++ = '\0';
377                         /* Set 'waiting for first entry character' flag */
378                         first_entry_char = 1;
379                 }
380         } while (i < MAX_ENTRIES);
381
382         return ( i );
383 }
384
385 /* NOTE: ANSI defines (1,1) as upper left corner, IBM BIOS uses (0,0)
386 --------------------------------------------------------------------------*/
387 void gotoxy (int x, int y)
388 {
389         printf ("\e[%d;%dH", y+1, x+1);
390 }
391
392 /*
393 Display screen border.
394 --------------------------------------------------------------------------*/
395 static void show_border ( void )
396 {
397         unsigned short i;
398         int disp_ul = DISP_UL;
399         int disp_ur = DISP_UR;
400         int disp_ll = DISP_LL;
401         int disp_lr = DISP_LR;
402         int disp_horiz = DISP_HORIZ;
403         int disp_vert = DISP_VERT;
404
405         /* Clear the screen, and set foreground and background attributes */
406         for (i = 0; i<rows; i++)
407         {
408                 gotoxy (0, i);
409                 printf ( "\e[K" );
410         }
411
412         /* Turn off the text cursor */
413         nocursor();
414
415         /* Show the top line of the border */   
416         gotoxy (menu_ulx, menu_uly);
417         ansi_putc (disp_ul);
418         for (i = 0; i < (menu_cols); i++)
419                 ansi_putc (disp_horiz);
420         gotoxy (menu_ulx+menu_cols+1, menu_uly);
421         ansi_putc (disp_ur);
422
423         /* Show the sides of the border */      
424         for (i = 0; i<menu_rows; i++)
425         {
426                 gotoxy (menu_ulx, menu_uly+1+i);
427                 ansi_putc (disp_vert);
428                 gotoxy (menu_ulx+menu_cols+1, menu_uly+1+i);
429                 ansi_putc (disp_vert);
430         }
431
432         /* Show the bottom line of the border */        
433         gotoxy (menu_ulx, menu_uly+menu_rows+1);
434         ansi_putc (disp_ll);
435         for (i = 0; i < (menu_cols); i++)
436                 ansi_putc (disp_horiz);
437         gotoxy (menu_ulx+menu_cols+1, menu_uly+menu_rows+1);
438         ansi_putc (disp_lr);
439
440         gotoxy ( 1, 0 );
441         printf ( "Network Free-Loader v" NFL_VERSION );
442 }
443
444 /*
445 --------------------------------------------------------------------------*/
446 static void pad_printxy ( char *szBuff, int x, unsigned short y, int len_entry, int reverse )
447 {
448         int i;
449 #if 0
450         gotoxy( 30, 20);
451         printf( "strlen:%d", strlen(szBuff));
452 #endif
453         for ( i=strlen(szBuff); (i < len_entry ); i++ )
454                 strcat( szBuff, " " );
455
456         gotoxy( x, y );
457         if ( reverse )
458                 printf ( "\e[7m%s\e[27m", szBuff );
459         else
460                 printf ( "%s", szBuff );
461 }
462
463 /*
464 --------------------------------------------------------------------------*/
465 static void show_entries ( unsigned short first_row, unsigned short num_rows )
466 {
467         unsigned short i, bot_entry;
468         int len_entry;
469         int disp_up = DISP_UP;
470         int disp_down = DISP_DOWN;
471         char szTmp[10], szBuff[80];
472
473 #ifdef SUPPORT_SERIAL
474         if (terminal & TERMINAL_SERIAL)
475         {
476                 disp_up = ACS_UARROW;
477                 disp_down = ACS_DARROW;
478         }
479 #endif /* SUPPORT_SERIAL */
480
481 /*      menu_cols = 25; */
482         len_entry = menu_cols;
483
484 /*  
485         bot_entry = top_entry+menu_rows-1;
486         if (bot_entry > num_entries)
487                 bot_entry = num_entries;
488 */      
489 #if 0
490         gotoxy( 30, 15 );
491         printf( "[%d]SE(1R:%d, #:%d, cur:%d, top:%d", dbg_count1++, first_row, num_rows, cur_entry, top_entry );
492 #endif
493 /*      for (i = 0; i <= bot_entry-top_entry; i++) { */
494         for (i = first_row; ((i < first_row+num_rows) && ((top_entry+i-1) <= num_entries)); i++) {
495
496                 *szTmp = 0;
497                 *szBuff = 0;
498
499                 if (number_entries) {
500                         if ((top_entry+i-1) <= 9)
501                                 strncat( szBuff, " ", len_entry );
502                         sprintf ( szTmp, "%d. ", (top_entry+i-1) );
503                         strncat( szBuff, szTmp, len_entry );
504 //                      len_entry -= strlen( szBuff );
505                 }
506         
507                 /* add the entry to the current string */
508                 strncat ( szBuff, menu_entries[top_entry+i-1], len_entry );
509                 pad_printxy( szBuff, menu_ulx+1, menu_uly+i, len_entry, ((top_entry+i-1) == cur_entry) );
510 #if 0
511                 if (i == first_row) {
512                         gotoxy( 30, 16);
513                         printf( "%d. %d [%s]", i, cur_entry, szBuff );
514                         gotoxy( 30, 17);
515                         printf( "                      " );
516                         gotoxy( 30, 18);
517                         printf( "                      " );
518                         gotoxy( 30, 19);
519                         printf( "                      " );
520                 }
521                 else
522                 if (i == first_row+1)
523                 {
524                         gotoxy( 30,17);
525                         printf( "%d. %d [%s]", i, cur_entry, szBuff );
526                 }
527 #endif
528         }
529         if (top_entry != 1) {
530                 *szBuff = 0;
531                 sprintf ( szBuff, " %c  ", disp_up );
532                 pad_printxy( szBuff, menu_ulx+1, menu_uly+1, len_entry, 0 );
533 #if 0
534                 gotoxy( 30, 18);
535                 printf( "%d. %d [%s]", i, cur_entry, szBuff );
536 #endif
537         }
538         if ((top_entry+menu_rows-1) < num_entries) {
539                 *szBuff = 0;
540                 sprintf ( szBuff, " %c  ", disp_down );
541                 pad_printxy( szBuff, menu_ulx+1, menu_uly+menu_rows, len_entry, 0 );
542 #if 0
543                 gotoxy( 30, 19);
544                 printf( "%d. %d [%s]", i, cur_entry, szBuff );
545 #endif
546         }
547 }
548
549
550 /*
551 --------------------------------------------------------------------------*/
552 static int select_entry ( void )
553 {
554         int c, c1, count=0;
555         unsigned short bot_entry;
556         unsigned short tmp;
557
558         ansi_reset();
559         enable_cursor(0);
560         printf ( "\e[3%dm\e[4%dm",  color_fg, color_bg );
561         gotoxy( 20, 5 );
562         
563         tmp = columns-5;
564         if (menu_cols > tmp)
565                 menu_cols = tmp;
566         tmp = rows-3;
567         if (menu_rows > tmp)
568                 menu_rows = tmp;
569
570         show_border ();
571
572         top_entry = 1;
573         cur_entry = 1;
574         bot_entry = top_entry+menu_rows-1;
575
576         show_entries ( 1, menu_rows );
577
578         while (1)
579         {
580                 if ((checkkey () != -1) || (timeout == -1)) 
581                 {
582                         /*
583                         Key was pressed, show which entry is selected before GETKEY,
584                         since we're comming in here also on TIMEOUT == -1 and
585                         hang in GETKEY 
586                         */
587                         c1 = getkey();
588
589                         c = translate_keycode( c1 );
590 #if 0
591                         gotoxy( 30, 10 );
592                         printf( "\e[K%d [%X]->[%X]", count++, c1, c );
593
594                         gotoxy( 30, 11 );
595                         printf( "cur:%d top:%d bot:%d rows:%d num_entries:%d", cur_entry, top_entry, bot_entry, menu_rows, num_entries );
596 #endif
597                         /* We told them above (at least in SUPPORT_SERIAL) to use
598                         '^' or 'v' so accept these keys.  */
599                         if (c == 16 || c == '^')
600                         {
601                                 if (terminal & TERMINAL_DUMB)
602                                 {
603                                         if (cur_entry > 1)
604                                                 cur_entry--;
605                                 } else {
606                                         if (cur_entry > 1)
607                                         {
608                                                 cur_entry--;
609                                                 if ((cur_entry == top_entry) && (top_entry > 1)) {
610
611                                                         top_entry--;
612                                                         bot_entry--;
613                                                         show_entries ( 1, (bot_entry-top_entry+1) );
614                                                 } else
615                                                         show_entries ( (cur_entry-top_entry+1), 2 );
616                                         }
617                                 }
618                         }
619                         if ((c == 14 || c == 'v') && ((cur_entry + 1) <= num_entries))
620                         {
621                                 if (terminal & TERMINAL_DUMB)
622                                         cur_entry++;
623                                 else {
624                                         if (cur_entry < num_entries)
625                                         {
626                                                 cur_entry++;
627                                                 if ((cur_entry >= bot_entry) && (bot_entry < num_entries)) {
628
629                                                         top_entry++;
630                                                         bot_entry++;
631                                                         show_entries ( 1, (bot_entry-top_entry+1) );
632                                                 }
633                                                 else
634                                                         show_entries( (cur_entry-top_entry), 2 );
635                                         }
636                                 }
637                         }
638                         if (c == '\r') {
639                                 return( cur_entry );
640                         }
641                 }
642         }
643 }
644
645 /*
646 Set new filename in bootp path
647 --------------------------------------------------------------------------*/
648 void set_file_to_load( struct bootp_t *bootp, char *file2load )
649 {
650         char    *p, *szPath = bootp->bp_file;
651
652         if (*file2load != '/')
653         {
654                 /* if reletive path, append to current path */
655                 if ((p = strrchr( szPath, '/')) != 0)
656                         szPath = p+1;
657         }
658         strcpy( szPath, file2load );
659 }
660
661 static void parse_elf_boot_notes(
662         void *notes, union infoblock **rheader, struct bootp_t **rbootp)
663 {
664         unsigned char *note, *end;
665         Elf_Bhdr *bhdr;
666         Elf_Nhdr *hdr;
667
668         bhdr = notes;
669         if (bhdr->b_signature != ELF_BHDR_MAGIC) {
670                 return;
671         }
672
673         note = ((char *)bhdr) + sizeof(*bhdr);
674         end  = ((char *)bhdr) + bhdr->b_size;
675         while (note < end) {
676                 unsigned char *n_name, *n_desc, *next;
677                 hdr = (Elf_Nhdr *)note;
678                 n_name = note + sizeof(*hdr);
679                 n_desc = n_name + ((hdr->n_namesz + 3) & ~3);
680                 next = n_desc + ((hdr->n_descsz + 3) & ~3);
681                 if (next > end) 
682                         break;
683 #if 0
684                 printf("n_type: %x n_name(%d): n_desc(%d): \n", 
685                         hdr->n_type, hdr->n_namesz, hdr->n_descsz);
686 #endif
687
688                 if ((hdr->n_namesz == 10) &&
689                         (memcmp(n_name, "Etherboot", 10) == 0)) {
690                         switch(hdr->n_type) {
691                         case EB_BOOTP_DATA:
692                                 *rbootp = *((void **)n_desc);
693                                 break;
694                         case EB_HEADER:
695                                 *rheader = *((void **)n_desc);
696                                 break;
697                         default:
698                                 break;
699                         }
700                 }
701                 note = next;
702         }
703 }
704
705 /*
706 --------------------------------------------------------------------------*/
707 int menu(struct ebinfo *eb, union infoblock *header, struct bootp_t *bootp)
708 {
709         int     i;
710
711         parse_elf_boot_notes(eb, &header, &bootp);
712         num_entries = scan_entries();
713         if (!num_entries) {
714                 printf( 
715                         "ERROR: No menu entries found\n"
716 #if 1
717                         "Rebuild menu program with a menu configuration file.\n"
718 #endif
719                         "Press any key:"
720                 );
721                 getchar();
722
723                 /* 2 = Tell Etherboot to retry */               
724                 return( 2 );
725         }
726         i = select_entry( );
727 #if 0
728         gotoxy( 30, 13 );
729         printf( "Selected entry:%d [%s]", i, menu_entries[i] );
730         getchar();
731 #else
732         console_cls();
733 #endif
734         set_file_to_load ( bootp,  menu_entries[ i ] );
735         return ( 1 );
736 }