PowerPC OpenFirmware Implementation Details: Difference between revisions
Added a quick stub bringing over the information from the POBARISNA wiki about PowerPC OpenFirmware client program implementation (and cleaning up the info a bit). Hopefully I can go in later and clean this up and make it more complete (I want someone to be able to read this and know enough to start doing Apple PowerPC baremetal dev right away). ~Sarah |
m Oops, leftover markdown |
||
Line 11: | Line 11: | ||
== The Client Interface == | == The Client Interface == | ||
Essentially, there are 3 ways to interact with OpenFirmware. There's a user interface, which is the | Essentially, there are 3 ways to interact with OpenFirmware. There's a user interface, which is the <pre>0 </pre> or <pre>ok </pre> prompt you've probably seen before when using OF-based systems, a device interface for things like PCI card BIOSs to interact with, and the client interface, for client programs (which you'll mainly be concerning yourself with when targeting OpenFirmware). | ||
To put it simply, the client interface is a set of functions that live inside OpenFirmware that client programs are meant to use to accomplish things like calling OF commands, reading information about devices, or manipulating the OpenFirmware built-in console and framebuffer. These functions (referred to as "services" in typical OpenFirmware parlance) are exposed to a client program through an entry point (described later on this page). Exactly what services are provided is outside of the scope of this page, but the IEEE-1275 standard linked above has a very thorough list of the services you should typically have available on any standards-compliant OpenFirmware/IEEE-1275-based system. | To put it simply, the client interface is a set of functions that live inside OpenFirmware that client programs are meant to use to accomplish things like calling OF commands, reading information about devices, or manipulating the OpenFirmware built-in console and framebuffer. These functions (referred to as "services" in typical OpenFirmware parlance) are exposed to a client program through an entry point (described later on this page). Exactly what services are provided is outside of the scope of this page, but the IEEE-1275 standard linked above has a very thorough list of the services you should typically have available on any standards-compliant OpenFirmware/IEEE-1275-based system. | ||
Line 17: | Line 17: | ||
=== Accessing the Client Interface from PowerPC C/C++ code === | === Accessing the Client Interface from PowerPC C/C++ code === | ||
There aren't actually all that many functions (usually referred to as services in OF parlance) that the standard defines for the | There aren't actually all that many functions (usually referred to as services in OF parlance) that the standard defines for the client interface. Different system-specific IEEE-1275 implementations can certainly implement whatever special services they want, but you obviously can't just rely on those being there on every system you're writing for. | ||
The process for calling | The process for calling client interface services depends on what platform you're on, but on PowerPC, your client program is passed an OpenFirmware entry point, with a signature that looks something like this: | ||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> |
Revision as of 04:52, 2 September 2022
This page aims to describe implementation details for OpenFirmware client programs on PowerPC platforms (with a specific focus on Apple machines). Much of this information is taken from the POBARISNA Wiki. There isn't a very complete high level overview of what actually needs to be implemented when creating OpenFirmware client programs, so the hope is that this page can provide something like that.
What is OpenFirmware?
OpenFirmware is an implementation of the IEEE-1275 Standard for Boot (Initialization Configuration) Firmware. The standard essentially describes a boot firmware with a FORTH interpreter built in, supporting a tree of devices) that various client programs can interact with.
Client Programs
A client program utilizes the OpenFirmware client interface to perform some task. Client programs don't need to be kernels or bootloaders, but in practice (and in the context of what you're probably using OpenFirmware for), they usually are. Generally speaking, most of the time you're only going to be running one client program ever in an OF session, but this isn't necessarily a rule; the reasons for this will be explained later, as it's out of the scope of what I want to explain to you here. The client program interface varies a lot between different IEEE-1275/OpenFirmware implementations on different architectures; as a general rule, core concepts stay mostly the same, but any code samples here are going to be for Apple PowerPC platforms.
The Client Interface
Essentially, there are 3 ways to interact with OpenFirmware. There's a user interface, which is the
0
or
ok
prompt you've probably seen before when using OF-based systems, a device interface for things like PCI card BIOSs to interact with, and the client interface, for client programs (which you'll mainly be concerning yourself with when targeting OpenFirmware).
To put it simply, the client interface is a set of functions that live inside OpenFirmware that client programs are meant to use to accomplish things like calling OF commands, reading information about devices, or manipulating the OpenFirmware built-in console and framebuffer. These functions (referred to as "services" in typical OpenFirmware parlance) are exposed to a client program through an entry point (described later on this page). Exactly what services are provided is outside of the scope of this page, but the IEEE-1275 standard linked above has a very thorough list of the services you should typically have available on any standards-compliant OpenFirmware/IEEE-1275-based system.
Accessing the Client Interface from PowerPC C/C++ code
There aren't actually all that many functions (usually referred to as services in OF parlance) that the standard defines for the client interface. Different system-specific IEEE-1275 implementations can certainly implement whatever special services they want, but you obviously can't just rely on those being there on every system you're writing for.
The process for calling client interface services depends on what platform you're on, but on PowerPC, your client program is passed an OpenFirmware entry point, with a signature that looks something like this:
int openfirm_entry(void *args)
This entry point is provided as a function pointer on PowerPC, in register "r5" on Apple systems. The entry point that gets branched to in POBARISNA looks like this, to give you an idea of how to access it:
extern "C" void external_kmain(uint32_t r3, uint32_t r4, int (*openfw)(void*))
To call a service, you need to pass in a struct with space for the name of the service, the number of args you're passing, the number of return values you're expecting back, and appropriately sized fields for those args and return values. As an example, the *client interface* `open` function call in POBARISNA looks something like this (modified and commented to help you understand better):
static struct {
// this is the field that'll hold the name of the service we want to call.
const char *cmd;
// this is the number of args we pass to OF
int num_args;
// this is the number of returns we want back
int num_returns;
// this is an argument
const char *device_name;
// this is a return value
int handle;
} args = {
"open", // we want to call "open"
1, // it takes 1 argument
1 // ...and returns 1 thing
};
// since device_name is an argument we're passing, we should set it to something.
args.device_name = device;
// now, call our entry point, passing in our args array.
special::openfirm(&args);
// now args.handle (our return value) holds the handle OF opened for us.
return args.handle;
As you can probably gather, the structure has a pretty strict... structure.
- name of service
- number of args (uint32)
- number of returns (uint32)
- num_args fields for arguments
- num_returns fields for return values
Thankfully, that's about all you need to do to actually start interacting with OpenFirmware, at least on PowerPC Macs. You'll be using this pattern a lot across your codebase, so get real used to it real fast. I suggest making all of these stubs right away so that you can easily just call any client service you want from C/C++/whatever language.
The Device Tree
If you've heard of device trees before, it may have been in the context of ARM/ARM64 platforms. This is where those came from! In fact, many of the conventions and standard device names have stayed almost exactly the same, so this section will be kept fairly brief, as documentation on modern flattened device trees is fairly easy to come by. However, a basic overview is as follows:
A single device/node in the tree can be thought of as this structure:
struct of_device {
of_device* parent;
of_device* next_sibling;
of_device* children[];
of_property* properties[];
};
Where an "of_property" is simply:
struct of_property {
of_property* next;
char name[32];
void* content;
uint32_t content_size;
};
These nodes (the "of_device" structure above) are arranged in a hierarchical tree-like structure. There are a few key devices that are present on (most) OpenFirmware systems. These are:
Name | Function |
---|---|
/chosen |
Carries important info about the system, such as stdin and stdout for the OpenFirmware console. |
/chosen/stdout |
The OpenFirmware console output. Can be written to with a call to the "write" client service. |
/memory |
Contains information about memory currently available to the system (size, number of banks, etc) |
/cpus |
Contains information about currently installed CPUs, such as clock speed, timer frequency, number of cores, etc. |
This is obviously not an extensive list of devices available to you when doing OpenFirmware development. Running "dev / ls" will give you a more exhaustive list.
Device types
All OpenFirmware devices fit into a few different categories, to make it easier to interact with them. These categories are:
Type | Purpose | Typical methods | Typical properties |
---|---|---|---|
display |
Could be something like a framebuffer, console output, etc. | open, close, write, draw-logo, restore |
N/A |
block |
Usually stuff like hard drives, etc. Random access. | open, close, read, write, seek, load |
N/A |
byte |
Sort of like block, but sequential access. (think a tape drive) | open, close, read, write, seek, load |
N/A |
network |
Network devices. | open, close, read, write, load |
local-mac-address, mac-address, address-bits, max-frame-size |
serial |
Serial devices | open, close, read, write, install-abort, remove-abort, restore, ring-bell |
N/A |
External Resources
These resources have been invaluable to Krabs on their OpenFirmware PPC journey:
IEEE-1275 Standard for Boot (Initialization Configuration) Firmware
Programming Environments Manual for 32-bit Implementations of the PowerPC Architecture
PowerPC Microprocessor Family: The Programming Environments Manual for 32 and 64-bit Microprocessors