Try to be like the turtle—at ease in your own shell.
—Bill Copeland
With the advent of an environment like UEFI, it would stand to reason that a common concept like a shell would arise. Conceptually, a shell is built “around” some aspect of a rather complex system and provides simplified abstractions for users to gain access to the underlying infrastructure. These users could be pieces of software (such as scripts and applications) or they could be humans interacting with the shell in an interactive manner.
A platform running a BIOS that is UEFI-compliant is what might be characterized as the “rather complex item” that a UEFI Shell is built around. The UEFI standards organization (www.uefi.orgn) publishes the UEFI and PI specifications, which drive the underlying architecture of the BIOS that runs in many of today’s platforms. This same organization has published a UEFI Shell Specification intended to guide what one can expect from a compliant UEFI Shell environment.
This chapter talks about various concepts, such as how the UEFI Shell is abstracting the underlying UEFI-compatible BIOS infrastructure, how certain concepts such as localization are accomplished within the shell, and the various manners in which a user can interact with the shell. It should also be noted that one of the most common uses of a shell today is to launch programs and/or scripts to enable some automated processing to occur. In many cases, DOS was a very common base for such types of activity (Figure 3.1).
The UEFI Shell is unusual in that it is not a shell that is a client of an operating system, but is actually considered a BIOS extension. This puts the shell on par with components that traditionally would be launched prior to an operating system such as an add-in device’s option ROM. Where the UEFI Shell is launched from is largely irrelevant, but for many platform vendors, the underlying feature set and size are important considerations since in some cases the shell may actually be contained in the platform’s FLASH device.
With the consideration that size and features are important to the platform vendor, the features that are provided by a UEFI Shell are likely even more important to the users of the shell. With this in mind, the concept of having varying levels of UEFI Shell support became very important along with the ability for a client of the UEFI Shell to determine what support was being provided.
The concept that a UEFI Shell can vary its support can be worrisome to some, but suffice it to say that this support is both predictable and easily dealt with. The shell is composed of two primary classes of contents:
Programmatic Shell Environment. This environment is guaranteed to remain available regardless of what underlying shell level is supported by a platform that purports to support the UEFI Shell. It is composed of the calling interfaces that shell applications can use.
Script Shell Environment. This environment is the one that supports the launching and interpreting of shell scripts. The biggest variation that one might witness between shell support levels is the enumeration of commands that are supported in a given support level.
The shell contains an environment variable known as the shellsupport variable. This variable can be used by shell applications as well as shell scripts to determine what the underlying UEFI Shell’s function support is.
In Table 3.1, the various levels of shell support are listed. This illustrates how at its simplest, the shell may be used strictly for purposes of shell applications to be launched (no scripting services). At level 1, basic scripting support is introduced, while level 2 simply adds a few more commands and functionality. In level 3, the concept of being “interactive” is introduced. For people who are familiar with the “C:” prompt from DOS, this interactive mode is similar in concept. Whereas in level 2, when a script was finished processing, the shell would terminate, in level 3, the shell provides a mode that allows the user to type at the UEFI Shell prompt.
Table 3.1: UEFI Shell Levels of Support
Note: * Noninteractive form only
Execute()
/Scripting/startup.nsh. Support indicates whether the Execute()
function is supported by the EFI_SHELL_PROTOCOL, whether or not batch scripts are supported, and whether the default startup script startup.nsh is supported.
PATH. Support determines whether the PATH environment variable will be used to determine the location of executables.
ALIAS. Support determines whether the ALIAS environment variable will be used to determine alternate names for shell commands.
Interactive. Support determines whether or not an interactive session can be started.
In many usage cases, bootable media is used to launch scripts or other utilities. Historically, the common components for bootable (removable) media were a floppy disk with DOS on it, some scripts, and possibly some executable utilities. DOS itself had some inherent limitations associated with a relatively weak API set compared to more modern environments, limited access to certain memory ranges, and other miscellaneous issues with more modern hardware environments. With the advent of UEFI systems, the same infrastructure can be launched as was done before (a DOS bootable image), but with relatively no discernable advantage—it simply preserves what was previously working. However, many users of bootable media (such as manufacturing operations, diagnostics, and so on) are actively porting their DOS solutions so that they can leverage the underlying UEFI BIOS environments.
Coupling UEFI-based BIOS with the UEFI Shell, a user can achieve a true advancement in what was done in prior solutions since any of the prior limitations associated with the DOS environment have been eliminated. In fact, since the infrastructure within which the UEFI Shell runs is robust, the utilities that are launched can fully leverage all of the UEFI BIOS APIs as well as the UEFI Shell infrastructure APIs in addition to running various sets of UEFI Shell scripts.
In some situations a user’s shell requirements are not compatible with what the platform currently supports. For those who are trying to provide solutions (utilities, scripts, and so on) that leverage the UEFI Shell and its environment, there are three situations to consider:
When the built-in UEFI Shell does not meet the solution’s requirements. If the UEFI Shell’s shellsupport level is insufficient for the solution provider’s needs, a copy of a UEFI Shell might need to be carried with the solution itself.
When there is no built-in UEFI Shell. There may be cases where the platform does not have a UEFI Shell built in as part of its feature set. With this in mind, the solution provider will want to carry a copy of a UEFI Shell along with its solutions carried on the provider’s media.
When the platform is not compatible with UEFI. Even though UEFI BIOS is being adopted in a rapid manner in the industry, some platforms will have no underlying UEFI support. To address this situation, Intel has provided to the open source community something known as the Developer’s UEFI Emulation (DUET). DUET is designed to provide a UEFI environment on a non-UEFI pre-boot system. This is achieved by creating an UEFI file image for a bootable device, and then “booting” that image as a legacy boot. On this same bootable device/media a solution provider can, in addition to providing UEFI emulation, provide a copy of the shell environment as well as any other material the solution provider desires.
Figure 3.2 illustrates three common usage scenarios for the UEFI Shell. The first is when the platform contains all the needed support for the script/utility solution, the second is when the underlying platform shell support is insufficient, and the third is when the platform is not UEFI-compatible.
Interfaces that are callable from binary programs are what form the UEFI Shell services. These services are what provide simplified access to various shell features and also simplify the interactions that shell clients would have with the underlying UEFI infrastructure. Figure 3.3 provides a high-level view of what the interactions would be between the UEFI infrastructure, shell interfaces, and shell clients.
Even though Appendix B has an exhaustive enumeration of the UEFI Shell script commands, and Appendix C has an exhaustive enumeration of the UEFI Shell environment interfaces, this chapter covers the basic programmatic capabilities, their relationship with the underlying infrastructure, and how they are practically used.
Two classes of operations occur within the UEFI Shell environment. One class of operations runs a script file that uses built-in shell commands (such as DIR and COPY). The other class of operations are binary programs that when launched can use a variety of underlying services.
An example of this interaction would be when a script executes a DIR shell command. When doing this, the following steps occur:
DIR command in a script file is interpreted by the Shell Interpreter.
Shell Interpreter then calls a Shell Protocol function such as OpenRoot()
.
The Shell Protocol would then call a UEFI service such as the UEFI Simple File System Protocol’s OpenVolume()
routine.
The UEFI Simple File System Protocol would then call other routines, which would ultimately interact directly with the hardware and return the requested information.
Figure 3.4 shows how a script that uses a UEFI Shell command will in turn interact with both the UEFI Shell interfaces and UEFI BIOS interfaces to achieve what is requested. It also shows that shell applications would also interact with the underlying UEFI Shell and UEFI BIOS interfaces.
One of the inherent capabilities that were introduced into UEFI 2.1 was the ability to easily construct applications that can seamlessly support multiple languages. It should be noted that the primary difference between what someone might call a standard UEFI driver/application and a UEFI Shell application is that the latter has knowledge of the programmatic components of the UEFI Shell infrastructure. That being said, UEFI Shell applications can leverage the underlying localization support in the same manner as any other BIOS component, such as UEFI Drivers and Option ROMs.
The concept of having a shell that is interactive is almost always assumed. The stereotypical scenario is the command prompt where a user might type a command and the results are printed to the screen (either locally or through a remote connection). With the advent of the shellsupport environment variable and the concept that a shell might have varying levels of support, it should not always be assumed that a shell will be interactive. In fact, it might be very common, based on the type of shell shipped with a given platform, that a script would launch and the shell environment would be closed as soon as the script was terminated (whether through a user-initiated event or the script completing).
In the interactive shell environment, the usage model for the UEFI Shell is similar to what would traditionally be thought of with most shells. Some of the basics that will be discussed are the launching of external binary applications, launching UEFI Shell scripts, and how the various UEFI Shell commands would ultimately resolve into programmatic interaction with the underlying UEFI Shell infrastructure as well as potentially interacting with the underlying UEFI firmware and hardware itself.
Depending on the reader’s background, three common terms might be used to represent this definition, “a list of commands that can be executed without requiring user interaction.” These terms are scripts, macros, or batch files, and to simplify things, this book will try to settle on the term scripts when referring to the aforementioned definition.
The UEFI Shell environment is responsible for parsing the script file and interpreting the contents sufficiently to understand what type of action it is being requested to proxy. Some of the basic operations that this environment would need to accomplish would be:
Execute UEFI Shell commands
Chaining of UEFI Shell scripts
Launch UEFI Shell applications
Launch UEFI applications/drivers
Figure 3.5 illustrates the various components that the UEFI Shell interpreter would interact with when processing a script file. In this illustration, we see an example of a script-based command being parsed by the interpreter itself. Ultimately the interpreter (depending on the command being parsed) would potentially end up calling some underlying UEFI firmware interfaces. In an example where a UEFI Shell application was launched, it in turn may end up calling programmatic UEFI Shell interfaces, which would then potentially interact with some underlying UEFI firmware interfaces.
It is common practice for script files to execute shell commands, which for purposes of the script, are considered part of the shell environment. However, it is also common practice to launch commands or other scripts in shell environments. The UEFI Shell environment is no exception. The concept of one script launching another is often termed chaining, and as long as the target script is accessible, a script can choose to launch any other script it has been programmed to launch.
However, there are some definite distinctions between launching a text-based script and launching a binary program. Most of these distinctions have to do with how arguments are passed and what is or isn’t accessible to a particular target program. Luckily enough, for most scripts, these distinctions are completely invisible and immaterial. Since many users who are creating binary programs will launch these programs with the UEFI Shell, they may want to understand how some of these interactions would work (for example, getting command-line arguments). The following section talks a bit more about the launching of binary programs and covers some of these underlying interactions.
It should be understood that the UEFI Shell is running within the scope of a UEFI-based firmware environment. This means that the shell itself is a UEFI-based component that complies with the descriptions that are laid out in the UEFI specification. That being said, binary programs launched by the shell will also be UEFI compatible. Since we are introducing the topic of launching UEFI programs, it should be noted that three distinct types of programs that would typically launched by the UEFI Shell:
UEFI Driver – This is a UEFI-compliant binary program that would follow the UEFI specification driver model. Upon launch, this program may remain in memory and install protocols or services that also remain resident in the system.
UEFI Application – This is a UEFI-compliant binary program. Upon exit, this application will be unloaded from memory.
Shell Application – This is a UEFI-compliant binary program. This program has the same primary characteristics as a typical UEFI application with the addition of having knowledge of how to interact with the underlying UEFI Shell environment.
Even though all of these programs are compliant with the UEFI specification, several characteristics may be unique to UEFI Shell applications.
When launching a shell application, there is an assumption that parameters would be able to be passed to the application in some fashion. Unlike many conventions where an argument count and array of argument values are directly passed to an application, in a UEFI environment the standard entry point does not consist directly of this kind of data.
When an application is launched in UEFI, sufficient data is passed to the application for it to gain access to the essential components of the UEFI environment. Figure 3.6 shows this standard entry point and how the data contained within this will also provide access to other essential material in the UEFI environment.
Figure 3.7 shows three main components:
Standard Entry Point – This is the fundamental starting point for all UEFI-compatible programs that exposes the underlying UEFI firmware services.
–Image Handle – When an application is loaded into memory for execution, this value will be the unique identifier for the application.
–System Table – The table that contains a variety of required data and also provides a means to acquire access to the runtime and boot services that UEFI provides.
Runtime and Boot Services – These are the callable interfaces that can be used to interact with the UEFI environment. These interfaces encompass several general classifications:
–Task Priority Services
–Memory Services
–Event/Timer Services
–Protocol Services
–Image Services
–Time Services
–Variable Services
–Miscellaneous Services
Shell Parameters Protocol – This is the protocol that is used in a shell environment to describe all of the command-line parameter data as well as standard handles for output, input, and error. An instance of this protocol is installed on the Image Handle of the application.
Figure 3.8 is an example of how one might leverage the standard entry point data to acquire information like the passed in command-line parameters:
Since many underlying functions such as acquisition of command-line data are normally abstracted by library services, the example in Figure 3.8 is good for showing how some of these fundamental pieces of data are interrelated. This is especially true since the ability to acquire the entry point for protocol services is key to creating a UEFI-aware application; and knowing how to acquire such data solely from the commonly passed-in entry point data will help not only for UEFI Shell programming but for any UEFI programming.
When programs are launched, they will have their returned status codes analyzed by the UEFI Shell environment and have an internal copy of the LastError environment variable updated with this result. Figure 3.8 shows that when a program exits (thus returning a status), the UEFI Shell environment will automatically be updated so that when a script checks %LastError%, it will automatically be reflected with this returned status.
In a traditional operating system where scripting is prevalent, a simple abstraction for file systems are usually available (such as, “C:”or “D:”). The UEFI Shell environment is no different than these traditional operating systems. In fact, it goes one step further in that it provides clear abstractions for LBA-based (sector-based) accesses through a block I/O interface, and it provides abstractions for file-system based accesses through the disk I/O interface. Figure 3.9 introduces two common UEFI protocols that have to do with abstracting storage devices.
In Figure 3.9, references to BLK and FS are used to note either a block or filesystem interface. These interfaces are constructed during the UEFI Shell environment’s initialization. These text-based references are used as simply text notations for various aspects of storage devices. When the UEFI Shell (and especially the MAP command) analyzes the UEFI environment, it searches for instances of EFI_BLOCK_IO_PROTOCOL
. This is a UEFI protocol that provides an LBA-based abstraction for a storage device. Upon discovery, it will tag each discovered instance with a unique name. These interfaces are a logical abstraction, which means that they abstract a range of physical sectors on some media, and are not necessarily providing access to the entire media.
In the example of BLK0, this is an abstraction used to designate the entire disk. That simply means that when a command references BLK0, the first sector is the real first sector of that disk, while the last sector is the real last sector of the disk. This would contrast with the usage model of a partition’s BLK instance such as BLK1.
In the example, BLK1 is associated with the data range for a particular partition entry. That simply means that when a command references BLK1, the first sector is the first data sector of that partition (not the disk), while the last sector is the last sector of the partition.
In addition to the discovery of block devices during the UEFI Shell initialization, the UEFI Shell will analyze the UEFI environment looking for instances of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
. This is a UEFI protocol that provides abstractions for recognized file-systems. Upon discovery, it will tag each discovered instance with a unique name. It should be noted that this protocol will not be established if the formatting of the media or partition is not recognized. For instance, if a media is formatted as a FAT32 file system, a UEFI system will layer an EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
instance.
When the UEFI Shell is executing a script, a lot of data needs to be interpreted. Figure 3.10 shows a very common example where a statement such as
COPY FS0:Source.txt FS0:Destination.txt
is interpreted by the UEFI Shell environment. The UEFI Shell initiates several steps during this interpretation:
EFI_SHELL_PARAME-TERS_PROTOCOL
on the target command’s image handle. (Recall that this is used for understanding the command-line parameters)The target command will then be responsible for several actions to complete the requested process.
FS0:Source.txt
) it will likely use the EFI_SHELL_PROTOCOL.OpenByFileName()
function to obtain a file handle.ReadFile
/WriteFile
).The UEFI Shell environment is ultimately responsible to handling the underlying functions associated with EFI_SHELL_PROTOCOL
. When calls are made to the functions in this protocol, several actions end up taking place.
OpenByFileName()
function to obtain a file handle, one of the key things is to determine where the file physically resides. This is determined by interpreting the passed-in data (for example, FS0:Source.txt
)FS0
) and it can in turn call the protocol EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
associated with that text-based shortcut.Source.txt
) and see if the file can be discovered.18.226.181.14