A package is a group of functions, or methods, that implements a specific interface. A package implements a library of functions that may then be called, as needed, by FCode programs.
For many devices, this is not particularly useful, but it will be useful for FCode programs that:
A plug-in package is a package that is not permanently resident in the main OpenBoot PROM. Plug-in packages are written in FCode. Since FCode is represented with a machine-independent binary format, it lets the same plug- in packages be used on machines with different CPU instruction sets.
A package's references to OpenBoot PROM system functions are resolved and the functions defined by the package are made available to other parts of the OpenBoot during the linking process. This is performed at run-time, when OpenBoot interprets (probes) the package. Thus, plug-in packages do not need to be pre-linked with a particular OpenBoot implementation.
OpenBoot only needs to know the beginning address of the package in order to probe it. Once probed, the package becomes a working part of OpenBoot, until the system is reset or turned off. A package exports its interface to OpenBoot, and to other packages, as a vocabulary of Forth words.
Many packages implement a specific interface; a standard set of functions. Different packages may implement the same interface. For example, there may be two display device driver packages, each implementing the standard display device interface, but for two different display devices.
There may also be multiple instances of a single package. For example, a plug- in disk driver may have as many instances as there are disks of that type.
A package consists of
Package data consists of uninitialized data, corresponding to Forth buffers, and initialized data, corresponding to Forth variables, values and deferred words. The initial values of the initialized data are stored within the package.
Each package is associated with exactly one device node, so you can use the terms package and device node interchangeably.
The active package is the package whose methods are currently visible.
An instance is a set of values for a package's data. Before a package's methods may be executed, an instance must be created. You create an instance from a package by allocating memory for the package's data and setting the contents of that memory to the initial values stored in the package. Multiple instances may be created from the same package, and may exist simultaneously.
The current instance is whatever instance is in use at a given time. When a package method accesses a data item, it refers to the copy of that data item that is associated with the current instance.
Plug-in device drivers are plug-in packages implementing simple device drivers. The interfaces to these drivers are designed to provide a primitive I/O capability.
Plug-in drivers are used for such functions as booting the operating system from that device, or displaying text on the device before the operating system has activated its own drivers. Plug-in drivers are made available to other parts of the OpenBoot PROM during the probing phase of the OpenBoot PROM start-up sequence.
Plug-in drivers must be programmed to handle portability problems, such as hardware alignment restrictions and byte ordering of external devices. With care, you can write a driver so that it is portable to a variety of systems in which the device could conceivably operate.
Plug-in drivers are intended to be stored in ROM located on the device itself, so that the act of installing the device automatically makes its plug-in driver available to the OpenBoot PROM.
For devices with no provision for such a plug-in driver ROM, the plug-in driver could be located elsewhere, perhaps in ROM located on a different device or in an otherwise unused portion of the main OpenBoot PROM.
A package that is intended for use by OpenBoot (bootable, for example) must always implement the two following methods:
Prepare the package for subsequent use. open typically allocates resources, maps, initializes devices, and performs a brief sanity check (no check at all may be acceptable). true is returned if successful, false if not. When open is called, the parent instance chain has already been opened, so this method may call its parent's methods.
Restore the package to its "not in use" state. close typically turns off devices, unmaps, and deallocates resources. close is executed before its parent is closed, so the parent's methods are available to close. It is an error to close a package which is not open.
The following methods are highly recommended. If possible, they should be present even if they are only stubs.
Put the package into a "quiet" state. reset is primarily for packages that do not automatically assume a quiet state after a hardware reset, such as devices that turn on with interrupt requests asserted.
Test the package. selftest is invoked by the OpenBoot test word. It returns 0 if no error found or a package-specific error number if a failure is noticed.
test does not open the package before executing selftest, so selftest is responsible for establishing any state necessary to perform its function prior to starting the tests, and for releasing any resources allocated after completing the tests. There should be no user interaction with selftest, as the word may be called from a program with no user present.
If the device was already open when selftest is called, a new instance will still be created and destroyed. A well-written selftest should handle this possibility correctly, if appropriate.
If the device is already open, but it is not possible to perform a complete selftest without destroying the state of the device, the integrity of the open device should take precedence, and the selftest process should test only those aspects of the device that can be tested without destroying device state. The inability to fully test the device should not be reported as an error result; an error result should occur only if selftest actually finds a device fault.
The "device already open" case happens most commonly for display devices, which are often used as the console output device, and thus remain open for long periods of time. When testing a display device that is already open, it is not necessary to preserve text that may already be on the screen, but the device state should be preserved to the extent that further text output can occur and be visible after selftest exits. Any error messages that are displayed by the selftest method will be sent to the console output device, so when testing an already-open display device, such error messages should be avoided during times when selftest has the device in a state where it is unable to display text.
selftest is not executed within an open/close pair. When selftest executes, a new instance is created (and destroyed). It will have its own set of variables, values, and so forth. These quantities are not normally shared with an instance opened with the normal open routine for the package.
Note - selftest should be written to do its own mapping and unmapping.
The usual Forth words can be used to create and use package data areas:
---------------------------
variable bar 5 value grinch defer stub create ival x , y , z , 7 buffer: foo ival foo 7 move ---------------------------
The data areas defined above are shared among all open instances of the package. If a value is changed, for instance, the new value will persist until it is changed again, independent of the creation and destruction of package instances.
All open instances of a package can access and change the value, which changes it for all other instances.
Usually a package does not share values among open instances. Consequently, you will usually want to use the following constructions to define package data areas local to a given package instance:
--------------------------
instance variable bar 5 instance value grinch instance defer stub 7 instance buffer: foo --------------------------
You should use the instance approach whenever possible. Using instance defines data areas that are re-initialized every time a package instance is created (usually by opening the package), so each instance gets its own copy of the data area. For example, changes to bar in one instance will not affect the contents of bar in another instance. (Note that create operates across all the instances, and cannot be made instance-specific.)
The total amount of data space consumed by that package is remembered as part of the package definition when finish-device executes to finish the package definition. Also, the contents of all the variables, values, and defers at the time finish-device executes are also stored as part of the package definition.
An instance of the package is created when that package is later opened. Data space is allocated for that instance (the amount of which was remembered in the package definition). The portion of that data space corresponding to the initialized variables, values, and defers is initialized from the values stored in the package definition. Data space associated with buffer:'s is not initialized.
You can add new methods and new properties to a package definition at any time, even after finish-device has been executed for that package. To do so, select the package and proceed to create definitions or properties.
However, it is not possible to add new data items to a package definition after finish-device has been executed for that package. finish-device sets the size of the data space for that package, and from then on it is fixed.
A particular package can often use the support of other, previously defined packages. There are two types of packages whose methods can be used directly:
A package definition is identified by its phandle. find-package returns the phandle of a package definition in the /packages node. The phandle is then used to open that support package. For example:
---------------------------
" deblocker" find-package ---------------------------
returns either false (not found), or phandle true.
Opening a support package with open-package returns an ihandle. This ihandle is used primarily to call the methods of the support package, and to close the support package when it is no longer needed.
An instance argument string must be supplied when opening any package (it may be null). The instance argument string can then be accessed from within the opened package with the my-args FCode (see below for details). For example (assume that phandle has already been found):
-----------------------------------------
" 5,3,0" phandle open-package (ihandle) -----------------------------------------
If the package cannot be opened, an ihandle of 0 is returned.
The following FCodes are used to find and open packages (within the /packages node):
Table 4-1 Package Access FCodes
----------------------------------------------------------------------------------------------------------------
Name Stack Comment Description ----------------------------------------------------------------------------------------------------------------
find-package ( name-adr name-len -- false | phandle true ) Find the package specified by the string name-adr name-len within /packages. Returns the phandle of the package, if found. open-package ( arg-adr arg-len phandle -- ihandle | 0 ) Open an instance of the package phandle, return the ihandle of the opened package, or 0 if unsuccessful. The package is opened with an instance argument string specified by arg-adr arg-len. $open-package ( arg-adr arg-len name-adr name-len -- ihandle | 0 ) Shortcut word to find and open a package within the /packages node in one operation. ----------------------------------------------------------------------------------------------------------------
An example of using $open-package follows:
-------------------------------
" 5,3,0" " deblocker" $open-package ( ihandle | 0 ) -------------------------------
Table 4-2 Manipulating phandles and ihandles
------------------------------------------------------------------------------------------------------------
Name Stack Comment Description ------------------------------------------------------------------------------------------------------------
my-self ( -- ihandle ) Return the instance handle of the currently-executing package instance. my-parent ( -- ihandle ) Return the instance handle of the parent of the currently-executing package instance. ihandlephandle ( ihandle -- phandle ) Convert an instance handle to a package handle. close-package ( ihandle -- ) Close an instance of a package. ------------------------------------------------------------------------------------------------------------
Don't confuse phandle with ihandle. Here's how to use them:
Use ihandlephandle to open another instance of the current package or its parent. my-self and my-parent return ihandles, which can be converted into phandles with ihandlephandle.
To open another instance of the current package, use:
--------------------------------------
my-self ihandlephandle open-package --------------------------------------
To open another instance of the parent package, use:
----------------------------------------
my-parent ihandlephandle open-package ----------------------------------------
An instance argument (my-args) is a string that is passed to a package when it is opened. The string may contain parameters of any sort, based on the needs of the package, or can simply be a null-string if no parameters are needed. A null string is generated either with " " or 0 0.
The instance argument passed may be accessed from inside the package with the my-args FCode.
Note - A package is not required to inspect the passed arguments.
If the argument string contains several parameters separated by a common character, you can pick off the pieces from within the package with left- parse-string. You can use any character as the separator; a comma is commonly used for this.
Note - Avoid using blanks or the / character, since these will confuse the parsing of any pathname.
A new value for my-args is passed every time a package is opened. This can happen under a number of circumstances:
The above three instances happen only once, when the package FCode is interpreted for the first time. If you want to preserve the initial value for my- args, the FCode program should copy it into a local buffer to preserve the information.
Whenever a package is reopened, a new value for my-args is supplied at that time. The method for supplying this new value depends on the method used to open the package, as described below.
------------------------------------------
ok " /sbus/SUNW,bwtwo:5,3,0" select-dev ok ------------------------------------------
A more complicated (and fictitious) example is the following:
---------------------------------------------------------------------
ok " /sbus/SUNW,fremly:test/grumpin@7,32:print/SUNW,fht:1034,5" ok select-dev ok ---------------------------------------------------------------------
Here the string "test" is passed to the SUNW,fremly package as it is opened, the string "print" is passed to the grumpin package as it is opened, and the string "1034,5" is passed to the SUNW,fht package as it is opened.
Another piece of information available to a package is its address relative to its parent package. Again, there are two main ways to pass this address to the package:
As an example of the first method, suppose the following package is being opened:
-------------------------------------
ok "/sbus/esp/sd@3,0:b" select-dev -------------------------------------
Then the address of the /sd package relative to the /esp package is 3,0. Note that this address must match the initial value of the "reg" property (if present) of the /sd package.
The package can find its relative address with my-unit, which returns the address as a pair of numbers. The first number (high) is the number before the comma in the example above, and the second number (low) is the number after the comma. Note that these are numbers, not strings.
As an example of the second method, suppose a test version of an FCode package is being interpreted:
-------------------------------------------
ok 0 0 " 3,0" " /sbus" begin-package ok -------------------------------------------
Here the my-args parameters for the new FCode are null, the initial address is 3,0 and it will be placed under the /sbus node.
The initial address can be obtained through my-address and my-space. Typically, you use my-space and my-address (plus an offset) to create the package's "reg" property, and also to map in needed regions of the device.
A method is identified by its execution token, which is returned by find- method for other packages. The token is actually the Forth acf for the word. For words in the package being defined, the Forth word ['] returns an execution token.
The execution token is used to execute a method in another package, and also to schedule a method for automatic, repeated execution by the system clock interrupt. See the alarm FCode.
Accessing the methods of a package can be done in one of the following ways (there are other ways as well, but these cover the common cases), with the last approach generally the best:
----------------------------------------------------
$open-package $call-method find-package open-package $call-method find-package open-package find-method call-package ----------------------------------------------------
Because finding is inherently a slow process, if a method is to be used repeatedly, the last technique is recommended. The idea is to save the ihandle and phandle of the package in question, together with the execution token of the method needed, so that the overhead of finding them gets paid only one time, instead of every time the method is executed.
For example, the following method is simple, but if slow called repeatedly:
-------------------------------------------------
: add-offset ( x.byte# -- x.byte#' ) my-args " disk-label" $open-package (ihandle) " offset" rot (name-adr name-len ihandle) $call-method ; -------------------------------------------------
A more complex, but if called repeatedly, much faster construct:
------------------------------------------------------------------------------------------
0 value label-ihandle \ place to save the other package's ihandle 0 value offset-method \ place to save found method's acf : init ( -- ) my-args " disk-label" $open-package ( ihandle ) is label-ihandle " offset" label-ihandle ihandlephandle ( name-adr name-len phandle ) find-method if ( acf ) is offset-method else ." Error: can't find method" then ; : add-offset ( d.byte# -- d.byte#' ) offset-method label-ihandle call-package ; ------------------------------------------------------------------------------------------
Because device access time often dominates I/O operations, the benefit of this extra code probably won't be noticed. It is only justified if the particular method will be called often.
A shortcut word to call a method in the parent package is $call-parent. This is equivalent to using my-parent $call-method.
Table 4-3 Method-Access Words
----------------------------------------------------------------------------------------------------------------
Name Stack Comment Description ----------------------------------------------------------------------------------------------------------------
find-method ( adr len phandle -- false | acf Find the method named adr len within the package phandle. true ) Returns false if not found. call-package ( [...] acf ihandle -- [...] ) Execute the method acf within the instance ihandle. $call-method ( [...] adr len ihandle -- [...] ) Shortcut word to find and execute the method adr len within the package instance ihandle. $call-parent ([...] adr len -- [...]) Execute the method adr len within the parent's package instance. Exactly equivalent to calling my-parent $call- method. ----------------------------------------------------------------------------------------------------------------
Mappings set up by a package persist across instances (unless explicitly unmapped). Passing the mapped addresses between instances is not usually worth the convolutions involved. It is usually better for each new instance to do its own mappings, being sure to unmap resources as they are no longer needed.
If you save virtual addresses into a value, be sure to use the instance declarations (see "Package Data Definitions" on page 41).
For example: assume a card in SBus slot#2 (named XYZ,me) needs custom attributes set by the user. nvramrc contents would include:
-----------------------------------------
probe-all cd /sbus/XYZ,me " type5" xdrstring " xyzmode" attribute device-end install-console banner -----------------------------------------
After editing nvramrc, turn on the nvram parameter use-nvramrc? and reset the machine to activate the contents of nvramrc. See OpenBoot Command Reference for more about editing nvramrc contents.
To modify the properties of a package, first probe the package to get it into memory. Normally, probing is done automatically after the nvramrc commands are executed.
See Chapter 5, "Properties", for more information about properties.
The /packages node of the device tree is special. It is a hierarchical node, but instead of describing a physical bus, /packages serves as a parent node for some software package nodes. The children of /packages are general-purpose software packages not attached to any particular hardware device. The "physical address space" defined by /packages is a trivial one: all addresses are the same - 0,0. Its children are distinguished by name alone.
The children of /packages are used by other packages to implement commonly used functions. They may be opened with the FCodes open- package or $open-package, and closed with close-package. There are three support packages that are included as standard children of /packages.
Disk (block) devices are random-access, block-oriented storage devices with fixed-length blocks. Disks may be subdivided into several logical "partitions", as defined by a disk label - a special disk block, usually the first one, containing information about the disk. The disk driver is responsible for appropriately interpreting a disk label. The driver may use the standard support package /disk-label if it does not implement a specialized label.
/disk-label interprets a standard Sun disk label, reading any "partitioning" information contained in it. It includes a first stage disk boot protocol for the standard label. load is the most important method defined by this package.
This package uses the read and seek methods of its parent (in practice, the package which opens this one to use the support routines). /disk-label defines the following methods:
Table 4-4 Sun Disk Label Package Methods
-------------------------------------------------------------------------------------------------------------------
Name Stack diagram Description -------------------------------------------------------------------------------------------------------------------
open (-- flag) Reads and verifies the disk label accessed by the read and seek methods of its parent instance. Selects a disk partition based upon the text string returned by my-args. For the standard Sun disk label format, the argument is interpreted as follows: Argument Partition <none'> 0 a or A 0 b or B 1 ... ... g or G 7 Returns -1 if the operation succeeds. As a special case, if the argument is the string "nolabel", open returns -1 (success) without attempting to read or verify the label. close (--) Frees all resources that were allocated by open. load (adr -- size) Reads a stand-alone program from the "standard" disk boot block location for the partition specified when the package was opened. Places the program at memory address adr, returning its length size. For the standard Sun disk format, the stand-alone program is 7.5K bytes beginning 512 bytes from the start of the partition. offset (x.rel-- x.abs) Returns the 64-bit absolute byte offset x.abs corresponding to the 64-bit partition-relative byte offset x.rel. In other words, adds the byte location of the beginning of the selected partition to the number on the stack. -------------------------------------------------------------------------------------------------------------------
The /obp-tftp package implements the Internet Trivial File Transfer Protocol (TFTP) for use in network booting. It is typically used by a network device driver for its first stage network boot protocol. Again, load is the most important method defined by this package.
This package uses the read and write methods of its parent, and defines the following methods:
Table 4-5 TFTP Package Methods
-----------------------------------------------------------------------------------------------------------
Name Stack diagram Description -----------------------------------------------------------------------------------------------------------
open (-- flag) Prepares the package for subsequent use, returning -1 if the operation succeeds and 0 otherwise. close (--) Frees all resources that were allocated by open. load (adr -- size) Reads the default stand-alone program from the default TFTP server, placing the program at memory address adr and returning its length size. For the standard Sun TFTP booting protocol, RARP (Reverse Address Resolution Protocol) is used to acquire the IP address corresponding to the system's MAC address (equivalent to its Ethernet address). From the IP address, the default file name is constructed, of the form <Hex-IP- Address>.<architecture> (for example, C0092E49.SUN4C). Then obp-tftp tries to TFTP read that file, first trying the server that responded to the RARP request, and if that fails, then broadcasting the TFTP read request. -----------------------------------------------------------------------------------------------------------
The /deblocker package makes it easy to implement byte-oriented device methods, using the block-oriented or record-oriented methods defined by devices such as disks or tapes. It provides a layer of buffering between the high-level byte-oriented interface and the low-level block-oriented interface. /deblocker uses the max-transfer, block-size, read-blocks and write-blocks methods of its parent, and defines the following methods:
Table 4-6 Deblocker Package Methods
---------------------------------------------------------------------------------------------------------------------
Name Stack diagram Description ---------------------------------------------------------------------------------------------------------------------
open (-- flag) Prepares the package for subsequent use, allocating the buffers used by the deblocking process based upon the values returned by the parent instance's max- transfer and block-size methods. Returns -1 if the operation succeeds, 0 otherwise. close (--) Frees all resources that were allocated by open. read (adr len -- actual) Reads at most len bytes from the device into the memory buffer beginning at adr. Returns actual, the number of bytes actually read, or 0 if the read operation failed. Uses the parent's read-blocks method as necessary to satisfy the request, buffering any unused bytes for the next request. write (adr len -- actual) Writes at most len bytes from the device into the memory buffer beginning at adr. Returns actual, the number of bytes actually read, or 0 if the write operation failed. Uses the parent's write-blocks method as necessary to satisfy the request, buffering any unused bytes for the next request. seek (x.position -- flag) Sets the device position at which the next read or write will take place. The position is specified by the 64-bit number x.position. Returns 0 if the operation succeeds or -1 if it fails. ---------------------------------------------------------------------------------------------------------------------