2 * Copyright (C) 2009 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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.
19 FILE_LICENCE ( GPL2_OR_LATER
);
30 #include <gpxe/dhcp.h>
31 #include <gpxe/keys.h>
32 #include <gpxe/timer.h>
33 #include <gpxe/process.h>
34 #include <usr/dhcpmgmt.h>
35 #include <usr/autoboot.h>
44 #define CPAIR_NORMAL 1
45 #define CPAIR_SELECT 2
47 /** A PXE boot menu item */
48 struct pxe_menu_item
{
49 /** Boot Server type */
58 * This structure encapsulates the menu information provided via DHCP
62 /** Prompt string (optional) */
64 /** Timeout (in seconds)
66 * Negative indicates no timeout (i.e. wait indefinitely)
69 /** Number of menu items */
70 unsigned int num_items
;
71 /** Selected menu item */
72 unsigned int selection
;
74 struct pxe_menu_item items
[0];
78 * Parse and allocate PXE boot menu
80 * @v menu PXE boot menu to fill in
81 * @ret rc Return status code
83 * It is the callers responsibility to eventually free the allocated
86 static int pxe_menu_parse ( struct pxe_menu
**menu
) {
87 struct setting pxe_boot_menu_prompt_setting
=
88 { .tag
= DHCP_PXE_BOOT_MENU_PROMPT
};
89 struct setting pxe_boot_menu_setting
=
90 { .tag
= DHCP_PXE_BOOT_MENU
};
91 uint8_t raw_menu
[256];
94 struct dhcp_pxe_boot_menu
*raw_menu_item
;
95 struct dhcp_pxe_boot_menu_prompt
*raw_menu_prompt
;
97 unsigned int num_menu_items
;
102 memset ( raw_menu
, 0, sizeof ( raw_menu
) );
103 if ( ( raw_menu_len
= fetch_setting ( NULL
, &pxe_boot_menu_setting
,
105 sizeof ( raw_menu
) ) ) < 0 ) {
107 DBG ( "Could not retrieve raw PXE boot menu: %s\n",
111 if ( raw_menu_len
>= ( int ) sizeof ( raw_menu
) ) {
112 DBG ( "Raw PXE boot menu too large for buffer\n" );
115 raw_menu_end
= ( raw_menu
+ raw_menu_len
);
117 /* Fetch raw prompt length */
118 raw_prompt_len
= fetch_setting_len ( NULL
,
119 &pxe_boot_menu_prompt_setting
);
120 if ( raw_prompt_len
< 0 )
123 /* Count menu items */
125 raw_menu_item
= ( ( void * ) raw_menu
);
127 if ( ( ( ( void * ) raw_menu_item
) +
128 sizeof ( *raw_menu_item
) ) > raw_menu_end
)
130 if ( ( ( ( void * ) raw_menu_item
) +
131 sizeof ( *raw_menu_item
) +
132 raw_menu_item
->desc_len
) > raw_menu_end
)
135 raw_menu_item
= ( ( ( void * ) raw_menu_item
) +
136 sizeof ( *raw_menu_item
) +
137 raw_menu_item
->desc_len
);
140 /* Allocate space for parsed menu */
141 *menu
= zalloc ( sizeof ( **menu
) +
142 ( num_menu_items
* sizeof ( (*menu
)->items
[0] ) ) +
143 raw_menu_len
+ 1 /* NUL */ +
144 raw_prompt_len
+ 1 /* NUL */ );
146 DBG ( "Could not allocate PXE boot menu\n" );
150 /* Fill in parsed menu */
151 (*menu
)->num_items
= num_menu_items
;
152 raw_menu_item
= ( ( ( void * ) (*menu
) ) + sizeof ( **menu
) +
153 ( num_menu_items
* sizeof ( (*menu
)->items
[0] ) ) );
154 memcpy ( raw_menu_item
, raw_menu
, raw_menu_len
);
155 for ( i
= 0 ; i
< num_menu_items
; i
++ ) {
156 (*menu
)->items
[i
].type
= le16_to_cpu ( raw_menu_item
->type
);
157 (*menu
)->items
[i
].desc
= raw_menu_item
->desc
;
158 /* Set type to 0; this ensures that the description
159 * for the previous menu item is NUL-terminated.
160 * (Final item is NUL-terminated anyway.)
162 raw_menu_item
->type
= 0;
163 raw_menu_item
= ( ( ( void * ) raw_menu_item
) +
164 sizeof ( *raw_menu_item
) +
165 raw_menu_item
->desc_len
);
167 if ( raw_prompt_len
) {
168 raw_menu_prompt
= ( ( ( void * ) raw_menu_item
) +
170 fetch_setting ( NULL
, &pxe_boot_menu_prompt_setting
,
171 raw_menu_prompt
, raw_prompt_len
);
173 ( ( raw_menu_prompt
->timeout
== 0xff ) ?
174 -1 : raw_menu_prompt
->timeout
);
175 (*menu
)->prompt
= raw_menu_prompt
->prompt
;
177 (*menu
)->timeout
= -1;
184 * Draw PXE boot menu item
186 * @v menu PXE boot menu
187 * @v index Index of item to draw
188 * @v selected Item is selected
190 static void pxe_menu_draw_item ( struct pxe_menu
*menu
,
191 unsigned int index
, int selected
) {
196 /* Prepare space-padded row content */
197 len
= snprintf ( buf
, sizeof ( buf
), " %c. %s",
198 ( 'A' + index
), menu
->items
[index
].desc
);
199 while ( len
< ( sizeof ( buf
) - 1 ) )
201 buf
[ sizeof ( buf
) - 1 ] = '\0';
204 row
= ( LINES
- menu
->num_items
+ index
);
205 color_set ( ( selected
? CPAIR_SELECT
: CPAIR_NORMAL
), NULL
);
206 mvprintw ( row
, 0, "%s", buf
);
211 * Make selection from PXE boot menu
213 * @v menu PXE boot menu
214 * @ret rc Return status code
216 static int pxe_menu_select ( struct pxe_menu
*menu
) {
218 unsigned int key_selection
;
225 init_pair ( CPAIR_NORMAL
, COLOR_WHITE
, COLOR_BLACK
);
226 init_pair ( CPAIR_SELECT
, COLOR_BLACK
, COLOR_WHITE
);
227 color_set ( CPAIR_NORMAL
, NULL
);
229 /* Draw initial menu */
230 for ( i
= 0 ; i
< menu
->num_items
; i
++ )
232 for ( i
= 0 ; i
< menu
->num_items
; i
++ )
233 pxe_menu_draw_item ( menu
, ( menu
->num_items
- i
- 1 ), 0 );
237 /* Highlight currently selected item */
238 pxe_menu_draw_item ( menu
, menu
->selection
, 1 );
240 /* Wait for keyboard input */
245 /* Unhighlight currently selected item */
246 pxe_menu_draw_item ( menu
, menu
->selection
, 0 );
249 if ( ( key
== CR
) || ( key
== LF
) ) {
250 pxe_menu_draw_item ( menu
, menu
->selection
, 1 );
252 } else if ( ( key
== CTRL_C
) || ( key
== ESC
) ) {
255 } else if ( key
== KEY_UP
) {
256 if ( menu
->selection
> 0 )
258 } else if ( key
== KEY_DOWN
) {
259 if ( menu
->selection
< ( menu
->num_items
- 1 ) )
261 } else if ( ( key
< KEY_MIN
) &&
262 ( ( key_selection
= ( toupper ( key
) - 'A' ) )
263 < menu
->num_items
) ) {
264 menu
->selection
= key_selection
;
265 pxe_menu_draw_item ( menu
, menu
->selection
, 1 );
277 * Prompt for (and make selection from) PXE boot menu
279 * @v menu PXE boot menu
280 * @ret rc Return status code
282 static int pxe_menu_prompt_and_select ( struct pxe_menu
*menu
) {
283 unsigned long start
= currticks();
285 unsigned long elapsed
;
290 /* Display menu immediately, if specified to do so */
291 if ( menu
->timeout
< 0 ) {
293 printf ( "%s\n", menu
->prompt
);
294 return pxe_menu_select ( menu
);
297 /* Display prompt, if specified */
299 printf ( "%s", menu
->prompt
);
301 /* Wait for timeout, if specified */
302 while ( menu
->timeout
> 0 ) {
304 len
= printf ( " (%d)", menu
->timeout
);
307 if ( key
== KEY_F8
) {
310 return pxe_menu_select ( menu
);
311 } else if ( ( key
== CTRL_C
) || ( key
== ESC
) ) {
321 elapsed
= ( now
- start
);
322 if ( elapsed
>= TICKS_PER_SEC
) {
331 /* Return with default option selected */
337 * Boot using PXE boot menu
339 * @ret rc Return status code
341 * Note that a success return status indicates that a PXE boot menu
342 * item has been selected, and that the DHCP session should perform a
343 * boot server request/ack.
345 int pxe_menu_boot ( struct net_device
*netdev
) {
346 struct pxe_menu
*menu
;
347 unsigned int pxe_type
;
348 struct settings
*pxebs_settings
;
349 struct in_addr next_server
;
353 /* Parse and allocate boot menu */
354 if ( ( rc
= pxe_menu_parse ( &menu
) ) != 0 )
357 /* Make selection from boot menu */
358 if ( ( rc
= pxe_menu_prompt_and_select ( menu
) ) != 0 ) {
362 pxe_type
= menu
->items
[menu
->selection
].type
;
367 /* Return immediately if local boot selected */
371 /* Attempt PXE Boot Server Discovery */
372 if ( ( rc
= pxebs ( netdev
, pxe_type
) ) != 0 )
376 pxebs_settings
= find_settings ( PXEBS_SETTINGS_NAME
);
377 assert ( pxebs_settings
);
378 fetch_ipv4_setting ( pxebs_settings
, &next_server_setting
,
380 fetch_string_setting ( pxebs_settings
, &filename_setting
,
381 filename
, sizeof ( filename
) );
382 return boot_next_server_and_filename ( next_server
, filename
);