Chapter 9. PXE Compliance

Table of Contents
Commonly Used Data Types
The PXE Boot Sequence
PXE Preboot Functions
TFTP Support Functions
UDP Support Functions
UNDI Support Functions

PXE is Intel's Preboot Execution Environment standard. This specifys a minimum level of functionality a boot ROM must supply to an IPL or a bootable image. These functions are broken down into 4 catagories. The Preboot, TFTP, UDP and the UNDI APIs. The preboot contains the overall control and information functions. The UDP API supports the User Datagram Protocol. The TFTP API supports the Trivial File Transport Protocol, and the UNDI API is Intel's name for the Universal Network Driver Interface.

There are two data structures used, the pxe structure is the newer one, and the older pxenv structure is to maintain compatability with old versions of the PXE specification. Also to maintain backward combatability, the pxenv_t structure must be passed in the es:bx registers. These two structures live on a paragraph boundary in the UNDI code segment. The pxe_t structure is found by the client application by scanning memory between the top of the free base memory, or 0xa0000, If the top of free base memory can't be determined, scan from 0xa0000 to 0x10000. The UNDI drivers usually exist in the 0x80000 - 0xa0000 range.

These are the functions required by the PXE standard for preboot. These are mostly oriented towards global control and information. All functions return either a PXENV_EXIT_SUCCESS or PXENV_EXIT_FAILURE in the AX register. Both GCC and VC++ put the return code from a C function in the AX register. All parameters passed to these functions must be a Far pointer.

The Intel PXE APIs were designed to use the Microsoft 16-bit C __cdecl parameter format. This also works with GCC, but slightly differently. The main thing is the calling convention and register usage need to match VC++ to correctly support a PXE compliant boot. The PXE API works based on an index into a table of functions, one for each procedure. Also, to simplify assembler programs, each function in the API only takes a single argument, the address of a custom structure for that function. Other than the PXENV_EXIT_SUCCESS or PXENV_EXIT_FAILURE return codes, the error status and any data is written by the called function for return to the calling program. There is a status field in each structure, which holds a 2 byte error code.

Example 9-1. Example of an PXE API call

      
      #include "pxenv.h"
      #include "pxenv_udp.h"

      pxenv_udp_open_t pxenv_udp_data;

      /* Set the IP number 192.203.188.1 */ 
      pxenv_udp_data.src_ip.num = 1922031881;
      pxenv_udp_data.src_ip.array = {3,3,3,1};

      /* Open the UDP connection. PXE is a global pointer to the PXEENV
      structure in memory */
     if ((PXE->EntryPtr)(PXENV_UDP_OPEN, &pxenv_udp_data) == PXENV_EXIT_FAILURE) {
        perror ("Couldn't open the UDP connection, error %s", pxenv_udp_data.Status);
	return PXENV_EXIT_FAILURE;
      } else {
	return PXENV_EXIT_SUCCESS;        
      }

      

All structures store their data in little endian (Intel) format unless specified otherwise.

Commonly Used Data Types

These are the shared data types used within the PXE library.

    
    #define IP_ADDR_LEN 4
    #define MAC_ADDR_LEN 16

    typedef union ip4_u {
        u_int32_t  num;
        u_int8_t   field[IP_ADDR_LEN];
    } ip4_t;

    typedef u_int8_t mac_addr_t[MAC_ADDR_LEN];
    typedef u_int16_t udp_port_t;
    typedef u_int16_t tftp_port_t;

    /* This is returned by all the functions in the API in the 
     * AX register */
    typedef enum pxenv_ret_code_e {
        PXENV_EXIT_SUCCESS,
	PXENV_EXIT_FAILURE
    } pxenv_ret_code_t;

    /* Real mode segment or protected mode selector. */
    typedef u_int16_t segsel_t; 

    /* Unsigned 16bit offset. */
    typedef u_int16_t off16_t;
    typedef u_int32_t addr32_t;
    typedef u_int16_t pxenv_exit_t;
    typedef u_int16_t pxenv_status_t;

    typedef struct segoff16_s {
        off16_t offset;
	segsel_t segment;
    } segoff16_t;

    typedef struct segdesc_s {
        u_int16_t limit_low;
	u_int16_t base_low;
	u_int8_t base_mid;
	u_int8_t type;
	u_int8_t limit_high;
	u_int8_t base_high;
    } segdesc_t;

    typedef struct newsegdesc_s {
        u_int16_t seg_addr;
	u_int32_t phy_addr;
	u_int16_t seg_size;
    } newsegdesc_t;

    /* Loader & BUSD parameter structures. */
    typedef struct bc_loader_s {
        pxenv_status_t status; 
	u_int16_t  _ax_;   
	u_int16_t  _bx_;   
	u_int16_t  _dx_;   
	u_int16_t  _di_;   
	u_int16_t  _es_;   
	u_int16_t  undi_romid_off; 
	u_int16_t  undi_romid_seg; 
    } bc_loader_t;

    typedef struct undi_loader_s {
        pxenv_status_t status; 
	u_int16_t  _ax_;   
	u_int16_t  _bx_;   
	u_int16_t  _dx_;   
	u_int16_t  _di_;   
	u_int16_t  _es_;   
	u_int16_t  undi_ds;        
	u_int16_t  undi_cs;        
	segoff16_t pxeptr;
	segoff16_t pxenvptr;
    } undi_loader_t;

    typedef struct busd_enable_s {
        pxenv_status_t status; 
	u_int16_t  _ax_;   
	u_int16_t  _bx_;   
	u_int16_t  _dx_;   
	u_int16_t  _di_;   
	u_int16_t  _es_;   
	u_int16_t  undi_romid_off; 
	u_int16_t  undi_romid_seg; 
    } busd_enable_t;

    typedef struct busd_disable_s {
        pxenv_status_t status; 
	u_int16_t  _ax_;   
	u_int16_t  _bx_;   
	u_int16_t  _dx_;   
	u_int16_t  _di_;   
	u_int16_t  _es_;   
	u_int16_t  undi_romid_off; 
	u_int16_t  undi_romid_seg; 
    } busd_disable_t;