iPXE
pxe_preboot.c
Go to the documentation of this file.
1 /** @file
2  *
3  * PXE Preboot API
4  *
5  */
6 
7 /* PXE API interface for Etherboot.
8  *
9  * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or any later version.
15  *
16  * This program is distributed in the hope that it will be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24  * 02110-1301, USA.
25  *
26  * You can also choose to distribute this program under the terms of
27  * the Unmodified Binary Distribution Licence (as given in the file
28  * COPYING.UBDL), provided that you have satisfied its requirements.
29  */
30 
31 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
32 
33 #include <stdint.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <ipxe/uaccess.h>
37 #include <ipxe/dhcp.h>
38 #include <ipxe/fakedhcp.h>
39 #include <ipxe/device.h>
40 #include <ipxe/netdevice.h>
41 #include <ipxe/isapnp.h>
42 #include <ipxe/init.h>
43 #include <ipxe/if_ether.h>
44 #include <basemem_packet.h>
45 #include <biosint.h>
46 #include <rmsetjmp.h>
47 #include "pxe.h"
48 #include "pxe_call.h"
49 
50 /* Avoid dragging in isapnp.o unnecessarily */
52 
53 /** Zero-based versions of PXENV_GET_CACHED_INFO::PacketType */
59 };
60 
61 /** A cached DHCP packet */
63  struct dhcphdr dhcphdr;
64  /* This buffer must be *exactly* the size of a BOOTPLAYER_t
65  * structure, otherwise WinPE will die horribly. It takes the
66  * size of *our* buffer and feeds it in to us as the size of
67  * one of *its* buffers. If our buffer is larger than it
68  * expects, we therefore end up overwriting part of its data
69  * segment, since it tells us to do so. (D'oh!)
70  *
71  * Note that a BOOTPLAYER_t is not necessarily large enough to
72  * hold a DHCP packet; this is a flaw in the PXE spec.
73  */
75 } __attribute__ (( packed ));
76 
77 /** A PXE DHCP packet creator */
79  /** Create DHCP packet
80  *
81  * @v netdev Network device
82  * @v data Buffer for DHCP packet
83  * @v max_len Size of DHCP packet buffer
84  * @ret rc Return status code
85  */
86  int ( * create ) ( struct net_device *netdev, void *data,
87  size_t max_len );
88 };
89 
90 /** PXE DHCP packet creators */
95 };
96 
97 /**
98  * Name PXENV_GET_CACHED_INFO packet type
99  *
100  * @v packet_type Packet type
101  * @ret name Name of packet type
102  */
103 static inline __attribute__ (( always_inline )) const char *
104 pxenv_get_cached_info_name ( int packet_type ) {
105  switch ( packet_type ) {
107  return "DHCPDISCOVER";
109  return "DHCPACK";
111  return "BINL";
112  default:
113  return "<INVALID>";
114  }
115 }
116 
117 /* The case in which the caller doesn't supply a buffer is really
118  * awkward to support given that we have multiple sources of options,
119  * and that we don't actually store the DHCP packets. (We may not
120  * even have performed DHCP; we may have obtained all configuration
121  * from non-volatile stored options or from the command line.)
122  *
123  * Some NBPs rely on the buffers we provide being persistent, so we
124  * can't just use the temporary packet buffer. 4.5kB of base memory
125  * always wasted just because some clients are too lazy to provide
126  * their own buffers...
127  */
129 #define cached_info __use_data16 ( cached_info )
130 
131 /**
132  * Construct cached DHCP packets
133  *
134  */
135 void pxe_fake_cached_info ( void ) {
136  struct pxe_dhcp_packet_creator *creator;
137  union pxe_cached_info *info;
138  unsigned int i;
139  int rc;
140 
141  /* Sanity check */
142  assert ( pxe_netdev != NULL );
143 
144  /* Erase any stale packets */
145  memset ( cached_info, 0, sizeof ( cached_info ) );
146 
147  /* Construct all DHCP packets */
148  for ( i = 0 ; i < ( sizeof ( pxe_dhcp_packet_creators ) /
149  sizeof ( pxe_dhcp_packet_creators[0] ) ) ; i++ ) {
150 
151  /* Construct DHCP packet */
152  creator = &pxe_dhcp_packet_creators[i];
153  info = &cached_info[i];
154  if ( ( rc = creator->create ( pxe_netdev, info,
155  sizeof ( *info ) ) ) != 0 ) {
156  DBGC ( &pxe_netdev, " failed to build packet: %s\n",
157  strerror ( rc ) );
158  /* Continue constructing remaining packets */
159  }
160  }
161 }
162 
163 /**
164  * UNLOAD BASE CODE STACK
165  *
166  * @v None -
167  * @ret ...
168  *
169  */
170 static PXENV_EXIT_t
171 pxenv_unload_stack ( struct s_PXENV_UNLOAD_STACK *unload_stack ) {
172  DBGC ( &pxe_netdev, "PXENV_UNLOAD_STACK\n" );
173 
174  unload_stack->Status = PXENV_STATUS_SUCCESS;
175  return PXENV_EXIT_SUCCESS;
176 }
177 
178 /* PXENV_GET_CACHED_INFO
179  *
180  * Status: working
181  */
182 static PXENV_EXIT_t
183 pxenv_get_cached_info ( struct s_PXENV_GET_CACHED_INFO *get_cached_info ) {
184  union pxe_cached_info *info;
185  unsigned int idx;
186  size_t len;
188 
189  DBGC ( &pxe_netdev, "PXENV_GET_CACHED_INFO %s to %04x:%04x+%x",
190  pxenv_get_cached_info_name ( get_cached_info->PacketType ),
191  get_cached_info->Buffer.segment,
192  get_cached_info->Buffer.offset, get_cached_info->BufferSize );
193 
194  /* Sanity check */
195  idx = ( get_cached_info->PacketType - 1 );
196  if ( idx >= NUM_CACHED_INFOS ) {
197  DBGC ( &pxe_netdev, " bad PacketType %d\n",
198  get_cached_info->PacketType );
199  get_cached_info->Status = PXENV_STATUS_UNSUPPORTED;
200  return PXENV_EXIT_FAILURE;
201  }
202  info = &cached_info[idx];
203 
204  /* Copy packet (if applicable) */
205  len = get_cached_info->BufferSize;
206  if ( len == 0 ) {
207  /* Point client at our cached buffer.
208  *
209  * To add to the fun, Intel decided at some point in
210  * the evolution of the PXE specification to add the
211  * BufferLimit field, which we are meant to fill in
212  * with the length of our packet buffer, so that the
213  * caller can safely modify the boot server reply
214  * packet stored therein. However, this field was not
215  * present in earlier versions of the PXE spec, and
216  * there is at least one PXE NBP (Altiris) which
217  * allocates only exactly enough space for this
218  * earlier, shorter version of the structure. If we
219  * actually fill in the BufferLimit field, we
220  * therefore risk trashing random areas of the
221  * caller's memory. If we *don't* fill it in, then
222  * the caller is at liberty to assume that whatever
223  * random value happened to be in that location
224  * represents the length of the buffer we've just
225  * passed back to it.
226  *
227  * Since older PXE stacks won't fill this field in
228  * anyway, it's probably safe to assume that no
229  * callers actually rely on it, so we choose to not
230  * fill it in.
231  */
232  get_cached_info->Buffer.segment = rm_ds;
233  get_cached_info->Buffer.offset = __from_data16 ( info );
234  get_cached_info->BufferSize = sizeof ( *info );
235  DBGC ( &pxe_netdev, " using %04x:%04x+%04x['%x']",
236  get_cached_info->Buffer.segment,
237  get_cached_info->Buffer.offset,
238  get_cached_info->BufferSize,
239  get_cached_info->BufferLimit );
240  } else {
241  /* Copy packet to client buffer */
242  if ( len > sizeof ( *info ) )
243  len = sizeof ( *info );
244  if ( len < sizeof ( *info ) )
245  DBGC ( &pxe_netdev, " buffer may be too short" );
246  buffer = real_to_user ( get_cached_info->Buffer.segment,
247  get_cached_info->Buffer.offset );
248  copy_to_user ( buffer, 0, info, len );
249  get_cached_info->BufferSize = len;
250  }
251 
252  DBGC ( &pxe_netdev, "\n" );
253  get_cached_info->Status = PXENV_STATUS_SUCCESS;
254  return PXENV_EXIT_SUCCESS;
255 }
256 
257 /* PXENV_RESTART_TFTP
258  *
259  * Status: working
260  */
261 static PXENV_EXIT_t
262 pxenv_restart_tftp ( struct s_PXENV_TFTP_READ_FILE *restart_tftp ) {
263  PXENV_EXIT_t tftp_exit;
264 
265  DBGC ( &pxe_netdev, "PXENV_RESTART_TFTP\n" );
266 
267  /* Words cannot describe the complete mismatch between the PXE
268  * specification and any possible version of reality...
269  */
270  restart_tftp->Buffer = PXE_LOAD_PHYS; /* Fixed by spec, apparently */
271  restart_tftp->BufferSize = ( 0xa0000 - PXE_LOAD_PHYS ); /* Near enough */
272  tftp_exit = pxenv_tftp_read_file ( restart_tftp );
273  if ( tftp_exit != PXENV_EXIT_SUCCESS )
274  return tftp_exit;
275 
276  /* Restart NBP */
278 }
279 
280 /* PXENV_START_UNDI
281  *
282  * Status: working
283  */
284 static PXENV_EXIT_t pxenv_start_undi ( struct s_PXENV_START_UNDI *start_undi ) {
285  unsigned int bus_type;
286  unsigned int location;
287  struct net_device *netdev;
288 
289  DBGC ( &pxe_netdev, "PXENV_START_UNDI %04x:%04x:%04x\n",
290  start_undi->AX, start_undi->BX, start_undi->DX );
291 
292  /* Determine bus type and location. Use a heuristic to decide
293  * whether we are PCI or ISAPnP
294  */
295  if ( ( start_undi->DX >= ISAPNP_READ_PORT_MIN ) &&
296  ( start_undi->DX <= ISAPNP_READ_PORT_MAX ) &&
297  ( start_undi->BX >= ISAPNP_CSN_MIN ) &&
298  ( start_undi->BX <= ISAPNP_CSN_MAX ) ) {
299  bus_type = BUS_TYPE_ISAPNP;
300  location = start_undi->BX;
301  /* Record ISAPnP read port for use by isapnp.c */
302  isapnp_read_port = start_undi->DX;
303  } else {
304  bus_type = BUS_TYPE_PCI;
305  location = start_undi->AX;
306  }
307 
308  /* Probe for devices, etc. */
309  startup();
310 
311  /* Look for a matching net device */
312  netdev = find_netdev_by_location ( bus_type, location );
313  if ( ! netdev ) {
314  DBGC ( &pxe_netdev, "PXENV_START_UNDI could not find matching "
315  "net device\n" );
317  return PXENV_EXIT_FAILURE;
318  }
319  DBGC ( &pxe_netdev, "PXENV_START_UNDI found net device %s\n",
320  netdev->name );
321 
322  /* Activate PXE */
323  pxe_activate ( netdev );
324 
325  start_undi->Status = PXENV_STATUS_SUCCESS;
326  return PXENV_EXIT_SUCCESS;
327 }
328 
329 /* PXENV_STOP_UNDI
330  *
331  * Status: working
332  */
333 static PXENV_EXIT_t pxenv_stop_undi ( struct s_PXENV_STOP_UNDI *stop_undi ) {
334  DBGC ( &pxe_netdev, "PXENV_STOP_UNDI\n" );
335 
336  /* Deactivate PXE */
337  pxe_deactivate();
338 
339  /* Prepare for unload */
340  shutdown_boot();
341 
342  /* Check to see if we still have any hooked interrupts */
343  if ( hooked_bios_interrupts != 0 ) {
344  DBGC ( &pxe_netdev, "PXENV_STOP_UNDI failed: %d interrupts "
345  "still hooked\n", hooked_bios_interrupts );
346  stop_undi->Status = PXENV_STATUS_KEEP_UNDI;
347  return PXENV_EXIT_FAILURE;
348  }
349 
350  stop_undi->Status = PXENV_STATUS_SUCCESS;
351  return PXENV_EXIT_SUCCESS;
352 }
353 
354 /* PXENV_START_BASE
355  *
356  * Status: won't implement (requires major structural changes)
357  */
358 static PXENV_EXIT_t pxenv_start_base ( struct s_PXENV_START_BASE *start_base ) {
359  DBGC ( &pxe_netdev, "PXENV_START_BASE\n" );
360 
361  start_base->Status = PXENV_STATUS_UNSUPPORTED;
362  return PXENV_EXIT_FAILURE;
363 }
364 
365 /* PXENV_STOP_BASE
366  *
367  * Status: working
368  */
369 static PXENV_EXIT_t pxenv_stop_base ( struct s_PXENV_STOP_BASE *stop_base ) {
370  DBGC ( &pxe_netdev, "PXENV_STOP_BASE\n" );
371 
372  /* The only time we will be called is when the NBP is trying
373  * to shut down the PXE stack. There's nothing we need to do
374  * in this call.
375  */
376 
377  stop_base->Status = PXENV_STATUS_SUCCESS;
378  return PXENV_EXIT_SUCCESS;
379 }
380 
381 /** PXE preboot API */
382 struct pxe_api_call pxe_preboot_api[] __pxe_api_call = {
384  struct s_PXENV_UNLOAD_STACK ),
386  struct s_PXENV_GET_CACHED_INFO ),
388  struct s_PXENV_TFTP_READ_FILE ),
390  struct s_PXENV_START_UNDI ),
392  struct s_PXENV_STOP_UNDI ),
394  struct s_PXENV_START_BASE ),
396  struct s_PXENV_STOP_BASE ),
397 };
void pxe_activate(struct net_device *netdev)
Activate PXE stack.
Definition: pxe_call.c:276
#define __attribute__(x)
Definition: compiler.h:10
Parameter block for pxenv_stop_base()
Definition: pxe_api.h:546
static const char * pxenv_get_cached_info_name(int packet_type)
Name PXENV_GET_CACHED_INFO packet type.
Definition: pxe_preboot.c:104
UINT16_t BufferLimit
Maximum buffer size.
Definition: pxe_api.h:296
PXENV_STATUS_t Status
PXE status code.
Definition: pxe_api.h:509
static PXENV_EXIT_t pxenv_restart_tftp(struct s_PXENV_TFTP_READ_FILE *restart_tftp)
Definition: pxe_preboot.c:262
struct arbelprm_rc_send_wqe rc
Definition: arbel.h:14
unsigned short uint16_t
Definition: stdint.h:11
Dynamic Host Configuration Protocol.
u32 info
Definition: ar9003_mac.h:67
#define PXENV_EXIT_FAILURE
An error occurred.
Definition: pxe_types.h:46
#define PXENV_START_UNDI
PXE API function code for pxenv_start_undi()
Definition: pxe_api.h:435
#define PXENV_STOP_UNDI
PXE API function code for pxenv_stop_undi()
Definition: pxe_api.h:505
uint16_t max_len
Maximum length (in bytes)
Definition: ntlm.h:18
Parameter block for pxenv_unload_stack()
Definition: pxe_api.h:253
int create_fakedhcpack(struct net_device *netdev, void *data, size_t max_len)
Create fake DHCPACK packet.
Definition: fakedhcp.c:136
#define DBGC(...)
Definition: compiler.h:505
UINT16_t PacketType
Packet type.
Definition: pxe_api.h:293
static struct pxe_dhcp_packet_creator pxe_dhcp_packet_creators[]
PXE DHCP packet creators.
Definition: pxe_preboot.c:91
static PXENV_EXIT_t pxenv_stop_undi(struct s_PXENV_STOP_UNDI *stop_undi)
Definition: pxe_preboot.c:333
Parameter block for pxenv_get_cached_info()
Definition: pxe_api.h:286
#define PXENV_PACKET_TYPE_DHCP_ACK
The DHCP server's DHCPACK packet.
Definition: pxe_api.h:276
PXE API entry point.
PXENV_EXIT_t pxenv_tftp_read_file(struct s_PXENV_TFTP_READ_FILE *tftp_read_file)
TFTP/MTFTP read file.
Definition: pxe_tftp.c:480
#define PXENV_START_BASE
PXE API function code for pxenv_start_base()
Definition: pxe_api.h:524
uint32_t buffer
Buffer index (or NETVSC_RNDIS_NO_BUFFER)
Definition: netvsc.h:16
#define rm_ds
Definition: libkir.h:39
Access to external ("user") memory.
#define ISAPNP_CSN_MAX
Definition: isapnp.h:76
UINT32_t BufferSize
Size of data buffer.
Definition: pxe_api.h:648
int create_fakedhcpdiscover(struct net_device *netdev, void *data, size_t max_len)
Create fake DHCPDISCOVER packet.
Definition: fakedhcp.c:109
#define ISAPNP_READ_PORT_MAX
Definition: isapnp.h:65
#define PXE_API_CALL(_opcode, _entry, _params_type)
Define a PXE API call.
Definition: pxe.h:108
BOOTPLAYER_t packet
Definition: pxe_preboot.c:74
#define PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC
Definition: pxe_error.h:74
PXENV_STATUS_t Status
PXE status code.
Definition: pxe_api.h:254
void pxe_fake_cached_info(void)
Construct cached DHCP packets.
Definition: pxe_preboot.c:135
pxe_cached_info_indices
Zero-based versions of PXENV_GET_CACHED_INFO::PacketType.
Definition: pxe_preboot.c:54
UINT16_t PXENV_EXIT_t
A PXE exit code.
Definition: pxe_types.h:44
#define BUS_TYPE_PCI
PCI bus type.
Definition: device.h:43
int pxe_deactivate(void)
Deactivate PXE stack.
Definition: pxe_call.c:307
assert((readw(&hdr->flags) &(GTF_reading|GTF_writing))==0)
PXENV_STATUS_t Status
PXE status code.
Definition: pxe_api.h:287
#define PXENV_STATUS_KEEP_UNDI
Definition: pxe_error.h:23
static PXENV_EXIT_t pxenv_start_base(struct s_PXENV_START_BASE *start_base)
Definition: pxe_preboot.c:358
#define ISAPNP_CSN_MIN
Definition: isapnp.h:75
A cached DHCP packet.
Definition: pxe_preboot.c:62
static struct net_device * netdev
Definition: gdbudp.c:52
UINT16_t BufferSize
Buffer size.
Definition: pxe_api.h:294
UINT16_t BX
bx register as passed to the Option ROM initialisation routine.
Definition: pxe_api.h:453
#define hooked_bios_interrupts
Definition: biosint.h:25
struct pxe_api_call pxe_preboot_api [] __pxe_api_call
PXE preboot API.
Definition: pxe_preboot.c:382
PXENV_STATUS_t Status
PXE status code.
Definition: pxe_api.h:547
char * strerror(int errno)
Retrieve string representation of error number.
Definition: strerror.c:78
struct net_device * pxe_netdev
Definition: pxe_undi.c:59
static __always_inline void copy_to_user(userptr_t dest, off_t dest_off, const void *src, size_t len)
Copy data to user buffer.
Definition: uaccess.h:324
A network device.
Definition: netdevice.h:352
#define PXENV_PACKET_TYPE_DHCP_DISCOVER
The client's DHCPDISCOVER packet.
Definition: pxe_api.h:273
#define PXENV_STATUS_UNSUPPORTED
Definition: pxe_error.h:22
static PXENV_EXIT_t pxenv_unload_stack(struct s_PXENV_UNLOAD_STACK *unload_stack)
UNLOAD BASE CODE STACK.
Definition: pxe_preboot.c:171
PXENV_STATUS_t Status
PXE status code.
Definition: pxe_api.h:528
SEGOFF16_t Buffer
Buffer address.
Definition: pxe_api.h:295
#define PXENV_RESTART_TFTP
PXE API function code for pxenv_restart_tftp()
Definition: pxe_api.h:418
#define ISAPNP_READ_PORT_MIN
Definition: isapnp.h:59
#define PXENV_UNLOAD_STACK
PXE API function code for pxenv_unload_stack()
Definition: pxe_api.h:250
#define PXENV_EXIT_SUCCESS
No error occurred.
Definition: pxe_types.h:45
A DHCP header.
Definition: dhcp.h:613
Network device management.
static PXENV_EXIT_t pxenv_get_cached_info(struct s_PXENV_GET_CACHED_INFO *get_cached_info)
Definition: pxe_preboot.c:183
char name[NETDEV_NAME_LEN]
Name of this network device.
Definition: netdevice.h:362
FILE_LICENCE(GPL2_OR_LATER_OR_UBDL)
Parameter block for pxenv_start_base()
Definition: pxe_api.h:527
uint32_t len
Length.
Definition: ena.h:14
A PXE API call.
Definition: pxe.h:81
#define PXENV_PACKET_TYPE_CACHED_REPLY
The Boot Server's Discover Reply packet.
Definition: pxe_api.h:283
#define __from_data16(pointer)
Definition: libkir.h:22
uint16_t isapnp_read_port
ISAPnP Read Port address.
Definition: pxe_preboot.c:51
Parameter block for pxenv_tftp_read_file()
Definition: pxe_api.h:645
ADDR32_t Buffer
Address of data buffer.
Definition: pxe_api.h:649
uint8_t data[48]
Additional event data.
Definition: ena.h:22
Format of buffer filled in by pxenv_get_cached_info()
Definition: pxe_api.h:322
#define PXENV_GET_CACHED_INFO
PXE API function code for pxenv_get_cached_info()
Definition: pxe_api.h:270
#define PXE_LOAD_PHYS
PXE physical load address.
Definition: pxe_call.h:24
Device model.
static __always_inline userptr_t real_to_user(unsigned int segment, unsigned int offset)
Convert segment:offset address to user buffer.
Definition: realmode.h:75
#define PXENV_STOP_BASE
PXE API function code for pxenv_stop_base()
Definition: pxe_api.h:543
Parameter block for pxenv_start_undi()
Definition: pxe_api.h:438
#define PXENV_STATUS_SUCCESS
Definition: pxe_error.h:19
#define rmlongjmp(_env, _val)
Definition: rmsetjmp.h:22
UINT16_t AX
ax register as passed to the Option ROM initialisation routine.
Definition: pxe_api.h:446
struct net_device * find_netdev_by_location(unsigned int bus_type, unsigned int location)
Get network device by PCI bus:dev.fn address.
Definition: netdevice.c:1029
PXENV_STATUS_t Status
PXE status code.
Definition: pxe_api.h:439
#define cached_info
Definition: pxe_preboot.c:129
static void shutdown_boot(void)
Shut down system for OS boot.
Definition: init.h:76
#define NULL
NULL pointer (VOID *)
Definition: Base.h:321
rmjmp_buf pxe_restart_nbp
Jump buffer for PXENV_RESTART_TFTP.
Definition: pxe_call.c:330
String functions.
UINT16_t DX
dx register as passed to the Option ROM initialisation routine.
Definition: pxe_api.h:462
int create_fakepxebsack(struct net_device *netdev, void *data, size_t max_len)
Create fake PXE Boot Server ACK packet.
Definition: fakedhcp.c:178
Parameter block for pxenv_stop_undi()
Definition: pxe_api.h:508
A PXE DHCP packet creator.
Definition: pxe_preboot.c:78
Fake DHCP packets.
void startup(void)
Start up iPXE.
Definition: init.c:67
static union pxe_cached_info __bss16_array(cached_info, [NUM_CACHED_INFOS])
static PXENV_EXIT_t pxenv_stop_base(struct s_PXENV_STOP_BASE *stop_base)
Definition: pxe_preboot.c:369
unsigned long userptr_t
A pointer to a user buffer.
Definition: uaccess.h:33
int(* create)(struct net_device *netdev, void *data, size_t max_len)
Create DHCP packet.
Definition: pxe_preboot.c:86
#define BUS_TYPE_ISAPNP
ISAPnP bus type.
Definition: device.h:46
void * memset(void *dest, int character, size_t len) __nonnull
static PXENV_EXIT_t pxenv_start_undi(struct s_PXENV_START_UNDI *start_undi)
Definition: pxe_preboot.c:284