PXE: Difference between revisions

[unchecked revision][unchecked revision]
Content deleted Content added
The PXE Article - think it is becoming good enough to move to the main namespace
m Bot: Replace deprecated source tag with syntaxhighlight
 
(13 intermediate revisions by 9 users not shown)
Line 1:
Preboot eXecution Environment, or PXE, is the only way for [[Diskless bootingBooting]] in PCs, and the standard is developed by Intel. It allows the Network Bootstrap Program (NBP) or the Bootloader to interface with the PXE, and load files via TFTP.
 
==State of the Machine==
Line 17:
The structure of the PXENV+ structure is as follows:
 
<sourcesyntaxhighlight lang="c">
// The Signature of the PXENV+ structure - contains "PXENV+".
uint8_t Signature[6];
Line 45:
uint16_t UNDIDataSeg;
uint16_t UNDIDataSize;
uint16_6uint16_t UNDICodeSeg;
uint16_t UNDICodeSize;
 
// This is a far pointer to the "!PXE" structure, only present when the structure is present.
uint32_t PXEPtr;
</syntaxhighlight>
</source>
 
==== Recommended Usage ====
Line 67:
The structure of the !PXE structure is as follows:
 
<sourcesyntaxhighlight lang="c">
// The Signature of the !PXE structure - contains "!PXE".
uint8_t Signature[4];
Line 90:
 
// The rest of the fields don't matter much (in the rest of the article), and the Specifications can be referred.
</syntaxhighlight>
</source>
 
==== Recommended Usage ====
Line 103:
==PXE API ==
 
The PXE Entry Point uses the Pascal__cdecl calling convention - push the parameters on the stack (rightmost pushed first, leftmost pushed last), far call the entry point, clean up the stack - while the older PXENV+ Entry Point uses registers.
 
To access the API, a generic CallAPI function can be created, which eases out the calling. To do this, first of all, the address of the Entry Point, from either of the structures should be stored in a location (PXEAPI in the example). The PXE API accept the same parameters:
Line 112:
A example generic call function is as follows:
 
<sourcesyntaxhighlight lang="asm">
; Calls the PXE API, and abstracts things such that it works on both old and new APIs.
; @ds:di The address of the input buffer.
Line 127:
add sp, 6 ; Clean up the stack.
ret
</syntaxhighlight>
</source>
 
The PXE API returns a status flag in AX and the beginning of the input buffer passed. It is recommended to test both of these, where a non zero value indicates failure.
Line 143:
The "t_PXENV_CACHED_INFO" follows the following structure:
 
<sourcesyntaxhighlight lang="asm">
; Note - due to comfort reasons, the author has decided to provide the following snippet in Intel (NASM) Assembly. If someone wishes to change it to a more generic format, he is welcome.
; in Intel (NASM) Assembly. If someone wishes to change it to a more generic format, he is welcome.
t_PXENV_GET_CACHED:
.Status dw 0
Line 152 ⟶ 153:
.BufferSeg dw 0
.BufferLimit dw 0
</syntaxhighlight>
</source>
 
* '''.Status'''. This should be zero, and returns the status after the function call.
Line 164 ⟶ 165:
To do a call, put the address of the above structure in ES:DI, put the opcode, 0x0071, in BX, and call the generic call function. A example call to the above function is, as follows:
 
<sourcesyntaxhighlight lang="asm">
mov di, t_PXENV_GET_CACHED
mov bx, GET_CACHED_INFO ; 0x0071.
Line 173 ⟶ 174:
test ax, ax
jnz .Error
</syntaxhighlight>
</source>
 
Bootph has a little complex structure, and the Specification should be referenced to know more about it. For now, the following is what you may require:
Line 191 ⟶ 192:
PXENV_TFTP_OPEN, with opcode 0x0020 is required to open a file via TFTP. It takes a t_PXENV_TFTP_OPEN structure as a parameter, which has the following format:
 
<sourcesyntaxhighlight lang="asm">
t_PXENV_TFTP_OPEN:
PXENV_TFTP_OPEN:
.Status dw 0
.SIP dd 0
Line 199 ⟶ 200:
.Port dw 0
.PacketSize dw 0
</syntaxhighlight>
</source>
 
* '''.Status'''. The status word.
Line 209 ⟶ 210:
* '''.Port'''. This should contain 69, or the UDP Port.
* '''.PacketSize'''. This contains the size of each packet read. Sizes of 512, 1024 and something small are recommended. Usually, bigger sizes load the file faster, but aren't supported much. See the section on performance below for more information relating to packet size.
 
NOTE: The SIP, GIP (0 means default) and Port are all big endian.
Line 219 ⟶ 220:
The t_PXENV_TFTP_READ has the following structure:
 
<sourcesyntaxhighlight lang="asm">
t_PXENV_TFTP_READ:
PXENV_TFTP_READ:
.Status dw 0
.PacketNumber dw 0
Line 226 ⟶ 227:
.BufferOff dw 0
.BufferSeg dw 0
</syntaxhighlight>
</source>
 
* '''.Status'''. This contains the status returned after the function call.
Line 236 ⟶ 237:
* '''.BufferOff/Seg'''. The address where you want to read the file packet.
 
What you want to do here is to keep reading packets from the file by increasing the '.BufferOff/Seg' address, and checking if the size of the packet read is less than the size negotiated in TFTP_OPEN. However,It aftercan somebe experienceconcluded that several implementations use 0 to indicate EOF, thewhile followingother hasimplementations beengive noticeda by'packet read' size less than the author:size negotiated.
 
With the above two pointspoint in mind, the following pseudo code can be used to read a file:
* Several implementations use 0 to indicate EOF, while other implementations give a 'packet read' size less than the size negotiated.
 
<sourcesyntaxhighlight lang="c">
* If you try to read a file after end of file has been encountered, then FILE_NOT_FOUND error, with status 0x3B is given.
while (bytes_left > 0)
 
With the above two points in mind, the following pseudo code can be used to read a file:
 
<source lang="c">
while(BytesLeft)
{
CallAPIcall_api();
if(Status == 0x3B)
Status = BytesRead = 0;
 
// AX register contains whether it failed or not, while Status contains the detailed status.
if(Status)
if (status || asm("ax"))
AbortBootpanic();
 
if (BytesReadbytes_read ==< 0PACKET_SIZE)
break;
 
BytesLeftbytes_left -= PACKET_SIZE;
}
</syntaxhighlight>
</source>
 
===Closing===
 
The PXENV_TFTP_CLOSE, with opcode 0x0021, is perhaps the simplest function. It requires a input buffer, with the following format, to be passed to it:
 
<syntaxhighlight lang="asm">
t_PXENV_TFTP_CLOSE:
if(.Status == 0x3B) dw 0
</syntaxhighlight>
 
* '''.Status'''. The Status returned after calling the function. The function would most probably ONLY fail if there is no active TFTP connection.
 
===Performance===
 
The original TFTP protocol is a very simple protocol (mostly because it was designed to be implemented easily on tiny embedded systems). The consequence is that it's relative slow (the "stop and wait" nature of it means that the network connection's bandwidth is significantly under-utilised). For larger files this can noticeably effect boot times.
 
There are 3 approaches for improving performance.
 
====TFTP Block Size====
 
In 1998 a "TFTP Blocksize option" was introduced. The basic idea is to improve performance by increasing the block/packet size (and reducing the number of data packets and the number of acknowledgement packets). This is supported by the PXE specification.
 
In theory when you open a file via "TFTP Open" you're supposed to set the "PacketSize" field to what you're requesting, and (where possible) PXE is supposed to negotiate an appropriate packet size with the TFTP server and then set the "PacketSize" field to size of the packet that both the PXE code in the client and the TFTP server agreed upon. This means that (e.g.) you should be able to set "PacketSize" to 65535 bytes and PXE will change it to the maximum size that is supported.
 
In practice this only works on some/most network cards, and some networking cards are broken and don't work correctly. For example, the implementation of PXE that comes with various RealTek cards will simply return an "Unknown error" if the requested packet size is too large (even though the PXE specification says otherwise, and even though PXE defines more appropriate status codes).
 
It is possible to work around this. The recommended work-around is:
* If the "Version" field of the PXENV+ structure is less than "version 2.1"; be cautious and use a 512 byte packet size.
* Use the "UNDI GET INFORMATION" function to get information about the network adapter. The returned information includes an MTU (Max. Transmission Unit) field. If this fails be cautious and use a 512 byte packet size.
* Check if the MTU field is sane (within a reasonable range). If it's not within a reasonable range be cautious and use a 512 byte packet size.
* Subtract the size of an IPv4 header (20 bytes), the size of the UDP header (8 byte), the size of the TFTP header (4 bytes) and 16 more bytes just in case from the MTU. This gives you the maximum packet size you can safely use.
 
====TFTP "Sliding Window"====
 
In 2015 a "TFTP Window Size" option was introduced. The basic idea here is to use "sliding window" (like TCP) instead of "stop and wait" to increase network bandwidth utilisation. Unfortunately, at this time I doubt any network cards support this.
 
====Advanced/Non-standard Approaches====
 
The PXE API exposes lower level functionality, including the ability to send arbitrary packets and to intercept received packets (via. a callback/"ISR"). In theory it would be possible for a boot loader to use these to implement any protocol it likes (e.g. use FTP or HTTP instead of TFTP) as long as the server is setup to support the protocol (in addition to TFTP that would still be needed to download the boot loader).
 
The most extreme approach would be to design your own protocol and implement your own server. In this case (in theory) you would be able to maximize network bandwidth utilisation, and the client could also offload processing to the server. With a massive amount of over-engineering you could significantly reduce boot times this way.
 
==Cleaning up ==
 
After you have finished reading files via PXE, the best idea would be to shutdown PXE and reclaim any memory it was using. PXE support is unloaded by using the "PXEAPI_UNDI_SHUTDOWN" function to restore the network adapter to it's default state, followed by the "PXENV_UNLOAD_STACK" function to unhook any IRQ handlers.
 
Lastly, PXE needs to prepare itself to be removed from memory (unhook interrupt 0x1A) using the "PXENV_STOP_UNDI" function (for PXE) or the "PXENV_UNDI_CLEANUP" function (for PXENV+).
 
Once PXE is unloaded, memory can be reclaimed by checking the UNDI code segment and UNDI data segment start addresses and sizes (in the PXENV+ data structure), calculating "start address + size" and selecting the highest value. This value is the "new" number of bytes of RAM starting at 0x00000000 (or the amount of conventional memory), and can be used directly and/or the value at 0x40:0x13 (or 0x00000413) can be changed to reflect the new number of 1 KB blocks of conventional memory.
 
Please note that it's probably a good idea to calculate the amount of conventional memory before you unload PXE, because some of the functions above may clear the segment addresses and sizes in the PXENV+ structure.
 
 
==See Also ==
Line 269 ⟶ 313:
===Articles ===
 
* [[Diskless bootingBooting]]
 
===External Links ===
 
*[http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf PXE Specification]
 
[[Category:Booting]]
[[Category:Bootloaders]]
[[Category:Network Protocols]]
[[de:PXE]]