Museum

Home

Lab Overview

Retrotechnology Articles

Online Manuals

⇒ Pdev(lib) — Sprite KS.390

Media Vault

Software Library

Restoration Projects

Artifacts Sought

Pdev  —  C Library Procedures

NAME

Pdev_Open, Pdev_Close, Pdev_SetDefaultHandler, Pdev_SetStreamHandler, Pdev_EnumStreams − Package for servicing pseudo-devices. 

SYNOPSIS

#include <pdev.h> Pdev_Token
Pdev_Open(name, realNamePtr, reqBufSize, readBufSize, service, clientData)
void
Pdev_Close(pdevToken)
int
Pdev_GetStreamID(pdevToken)
int (∗
Pdev_SetDefaultHandler(pdevToken, operation, handler))()
int (∗
Pdev_SetStreamHandler(streamPtr, operation, handler))()
int
Pdev_EnumStreams(pdevToken, func, clientData)

ARGUMENTS

char ∗name   (in) Name of file to use for pseudo-device. 

char ∗∗realNamePtr   (out) Where to store pointer to actual pseudo-device file name, or NULL if name is to be the complete name of pseudo-device file. 

int reqBufSize   (in) The preferred size for request buffers. 

int readBufSize   (in) The size for a read buffer.  Zero means no read buffering. 

Pdev_CallBacks ∗service   (in) A set of service call-back procedures. 

ClientData clientData   (in) Private user-defined data field. 

Pdev_Token pdevToken   (in) Token for the pseudo-device returned from Pdev_Open. 

Pdev_Stream ∗streamPtr   (in) Handle for a stream to the pseudo-device. 

int operation   (in) PDEV_OPEN, PDEV_CLOSE, PDEV_READ, PDEV_WRITE, PDEV_IOCTL, PDEV_SET_ATTR, PDEV_GET_ATTR. 

int (∗handler)()   (in) Service call-back procedure. 

int (∗func)()   (in) A procedure applied to each stream to the pseudo-device. 

Pdev_Open

Pdev_Open creates a pseudo-device and installs a set of service procedures for it.  The pseudo-device can subsequently be opened by any number of regular (client) processes, and the service call-backs are made each time a client process makes a file system operation on the pseudo-device.  Thus the service call-backs implement the standard file system operations for the pseudo-device while the Pdev package manages the interface between the kernel and the server process. 

There are two ways that Pdev_Open can pick the name of the file to use for the pseudo-device.  If realNamePtr is NULL, then Pdev_Open uses name as the name.  If realNamePtr isn’t NULL, then Pdev_Open will generate a file name of the form hostDir/nameXX, where hostDir is the name of a standard host-specific directory, name is the parameter to this procedure, and XX is a decimal number generated by Pdev_Open.  Pdev_Open tries numbers up from 1 until it finds one that works.  The name of the successful pseudo-device file is returned by storing a pointer to it at ∗realNamePtr;  the storage for the name is dynamically allocated with malloc and must eventually be freed by the caller. 

Pdev_Open returns an opaque token that is used in calls to Pdev_Close, Pdev_SetDefaultHandler, and Pdev_EnumStreams.  If a pseudo-device couldn’t be opened, then NULL is returned and pdev_ErrorMsg contains a string describing the problem. 

After a successful Pdev_Open call the Pdev package will set up a service stream whenever a client process opens the pseudo-device.  Each service stream is identified to the call-backs by a Pdev_Stream record.  Thus the pseudo-device can be multiplexed over several clients with each client’s request comming over a different service stream.  However, forks and dups are not visible to the pseudo-device server, so more than one process might be using any particular service stream. 

The reqBufSize is used to configure a request buffer associated with each service stream.  This size determines how many request messages can be buffered before the kernel is forced to wait for them to be serviced.  More than one request may be outstanding due to asynchronous writes, which are described below.  A minimum size on the request buffer is enforced by the library, so zero can be passed in to get a default size (about 1 Kbyte). 

The readBufSize is used to configure an optional read buffer associated with each service stream.  If this size is non-zero it indicates that a read buffer will be used to satisfy client read requests instead of using the read service call-back.  In this case the Pdev package will allocate a read buffer each time a service stream is created and pass the address of this buffer to the open call-back.  After that it is up to the server process to manage the read buffer.  See the device man page for pdev for details. 

The clientData parameter to Pdev_Open is passed to the open call-back as described below.  It is meant to be used as a pointer back to some top-level state of the pseudo-device. 

The Pdev package uses the facilities of Fs_Dispatch in order to keep track of the streams associated with the pseudo-device and ensure that Pdev is notified whenever those streams become readable.  In order to use Pdev, you must also use Fs_Dispatch. 

Pdev_Close

Pdev_Close shuts down a pseudo-device, closing all the streams associated with it and releasing any resources allocated to the pseudo-device.  As a side-effect the close call-back is made to any existing service streams.  After this procedure returns, pdevToken should never be used again. 

Pdev_GetStreamID

Pdev_GetStreamID  returns the identifier for the stream associated with the token returned by Pdev_Open.  This may be used for stream-oriented calls such as fstat but should not be used as the argument to close (Pdev_Close should be used instead.) 

Pdev_EnumStreams

The Pdev_EnumStreams procedure is used to apply a function to all the service streams to the pseudo-device.  This enumeration procedure eliminates the need to keep track of each service stream.  The func argument is called on each service stream as follows: int (∗func)(streamPtr, clientData)
    Pdev_Stream ∗streamPtr;
    ClientData clientData;
 
Where streamPtr identifies the service stream, and clientData is what was passed to Pdev_EnumStreams.  func should return zero to mean success, or a non-zero error status.  In the case of an error Pdev_EnumStreams stops its enumeration and returns the non-zero status. 

Pdev_SetDefaultHandler

Pdev_SetDefaultHandler is used to set the call-back for individual pdev operations.  It is not normally needed as you can define all the call-backs with Pdev_Open (or Pfs_OpenConnection).  The call-backs passed to Pdev_Open are inherited by each service stream that is created.  Changing a call-back with Pdev_SetDefaultHandler changes the call-back for all subsequently created service streams.  It doesn’t affect any service streams that are already established.  This returns the old default call-back. 

Pdev_SetStreamHandler

Pdev_SetStreamHandler is used to set a call-back for an already existing service stream.  It returns the old call-back. 

SERVICE PROCEDURES

The call-back service procedures are given to Pdev_Open (and Pfs_OpenConnection) as a record of procedures: typedef struct {
    int (∗open)();/∗ PDEV_OPEN ∗/
    int (∗read)();/∗ PDEV_READ ∗/
    int (∗write)();/∗ PDEV_WRITE and PDEV_WRITE_ASYNC ∗/
    int (∗ioctl)();/∗ PDEV_IOCTL ∗/
    int (∗close)();/∗ PDEV_CLOSE ∗/
    int (∗getAttr)();/∗ PDEV_GET_ATTR ∗/
    int (∗setAttr)();/∗ PDEV_SET_ATTR ∗/ } Pdev_CallBacks;

Any of the record elements can be NULL to indicate that the operation should be handled by a default handler.  The service parameter itself can also be NULL to indicate default handling for all operations.  This is only useful during initial test.  If a client makes an operation for which no service procedure is provided it is simply a no-op; it is not an error.  The global variable pdev_Trace can be set to a non-zero value to generate printfs to stderr when each service procedure (default or user-supplied) is invoked. 

Service procedures should return zero to mean successful completion, otherwise they should return an appropriate errno value.  Additionally, the read and write procedures use EWOULDBLOCK to indicate incomplete operations.  This is described further below. 

Each service procedure also sets the current select state bits for the pseudo-device.  The select bits are used in the kernel’s implementation of select for pseudo-devices.  They should be a bitwise or combination of FS_READABLE, FS_WRITABLE, and FS_EXCEPTION.  As well as setting this select state after each client operation, it may be set asynchronously with the IOC_PDEV_READY ioctl command on the service stream. 

These same service procedures are used for pseudo-device connections into the pseudo-file-system.  See Pfs_Open and Pfs_OpenConnection.  The getAttr and setAttr call-backs are only made to pseudo-file-system servers.  For regular pseudo-devices the kernel takes care of all attribute handling. 

open

int (∗service->open)(clientData, streamPtr, readBuffer, flags, procID,  hostID, uid, selectBitsPtr)
    ClientData clientData;/∗ Private data passed to Pdev_Open ∗/
    Pdev_Stream ∗streamPtr;/∗ Identifies stream to pseudo-device. ∗/
    char ∗readBuffer;/∗ Storage for optional read buffer ∗/
    int flags;/∗ Flags to the open system call. NOTE!    ∗ These are Sprite flags defined in <fs.h>,   ∗ not the Unix flags defined in <sys/file.h> ∗/
    int procID;/∗ ID of process opening the pseudo-device ∗/
    int hostID;/∗ Host where that process is executing ∗/
    int uid;/∗ User ID of that process ∗/
    int ∗selectBitsPtr;/∗ Return - the initial select state of the process ∗/

When a client process makes an open system call on the pseudo-device the Pdev library package invokes the open service call-back to give the server a chance to refuse or accept the open by the client process.  The return value of the open call-back is either 0 for success, or an appropriate errno value. 

The open call-back gets passed the clientData that was given to the Pdev_Open procedure, and a new streamPtr that is a handle on the service stream corresponding to the open by the client.  streamPtr is a pointer to a Pdev_Stream record that contains a clientData field for use by the call-backs, and a streamID field that is used in ioctl calls on the service stream.  The possible ioctl calls are listed at the end of this man page.  The streamPtr gets passed to all the other call-backs, and is also passed to Pdev_SetStreamHandler. 

The parameters also include the useFlags passed to the Fs_Open system call, and the user ID and Sprite hostID of the client process.  (Fs_Open is the Sprite version of open.  The flag bits are different and are defined in <fs.h>.  Flags passed to open are mapped to the Sprite flag bits you’ll get here.)  If the readBufSize parameter to Pdev_Open was non-zero then Pdev allocates readBuffer and passes it to the open call-back.  Thus there will be one read buffer for each service stream if the server is implementing read buffering. 

close

int (∗service->close)(streamPtr)
    Pdev_Stream ∗streamPtr;/∗ Identifies service stream ∗/

This is called when a service stream is closed.  This happens either as a side effect of Pdev_Close, or when the client has closed is last reference to the service stream.  (Dups and forks are not visible to the pseudo-device server, so there is only one close per open system call by a client process.) 

read

int (∗service->read)(streamPtr, readPtr, freeItPtr, selectBitsPtr, sigPtr)
    Pdev_Stream ∗streamPtr;/∗ Identifies service stream ∗/
    Pdev_RWParam ∗readPtr;/∗ Read parameter block ∗/
    Boolean ∗freeItPtr;/∗ Set to TRUE if buffer should be free’d ∗/
    int ∗selectBitsPtr;/∗ Return - select state of the pseudo-device ∗/
    Pdev_Signal ∗sigPtr;/∗ Return - signal to generate, if any ∗/

The read service procedure is passed a record of type Pdev_RWParam that indicates the length, offset, and buffer for the read.  The buffer is pre-allocated by the Pdev library.  If the read service procedure wants to use a different buffer it can change readPtr->buffer to reference its own storage.  If this different storage area ought to be freed after the library completes the operation, then ∗freeItPtr should be set to a non-zero value. 

The readPtr->length record field indicates how much data is requested, and it should be updated to reflect the amount of data actually returned.  If there is no data available on the pseudo-device then the read call-back should return EWOULDBLOCK and set readPtr->length to zero.  This causes the kernel to block the client process until the select state of the pseudo-device is changed to indicate readability.  If there are some bytes available the return value should be zero and readPtr->length set appropriately.  If the read leaves no additional bytes available then the FS_READABLE bit can be cleared from ∗selectBitsPtr in order to block the next read request.  End-of-file is indicated to the client by a zero return code and a zero number of bytes returned. 

A signal can be generated in response to a read request by setting sigPtr->signal to a non-zero value.  sigPtr->code can also be set to modify the signal meaning.  Data can be returned if a signal is generated.  The client application’s system call will complete, its signal handler, if any, will be invoked, and the system call will be retried. 

Note:  If there is a read buffer associated with the service stream, which is indicated by a non-zero valued readBufSize parameter to Pdev_Open, then this read call-back is never called.  Instead the kernel takes data directly from the read buffer.  The protocol for adding data to the read buffer is described in the pdev device man page. 

write

int (∗service->write)(streamPtr, async, writePtr, selectBitsPtr, sigPtr)
    Pdev_Stream ∗streamPtr;/∗ Identifies service stream ∗/
    int async;/∗ TRUE during an asynchronous write ∗/
    Pdev_RWParam ∗writePtr;/∗ Write parameter block ∗/
    int ∗selectBitsPtr;/∗ Return - select state of the pseudo-device ∗/
    Pdev_Signal ∗sigPtr;/∗ Return - signal to generate, if any ∗/

The write service procedure is passed a parameter block that indicates the length, offset, and buffer for the operation, plus various IDs of the application process.  If async is FALSE (zero) then writePtr->length should be updated to reflect how much data was processed by the service procedure.  If async is non-zero it indicates an asynchronous write and the service procedure must accept all of the data and the return value of writePtr->length is ignored. 

If the server cannot accept all of the data it must return EWOULDBLOCK and update writePtr->length to indicate just how much data it accepted.  This return value causes the kernel to block the client process until the select state of the pseudo-device is changed to indicate writability.  To repeat, returning a short write count and a zero return code will cause the kernel to immediately issue another write request to complete the client’s write operation.  By also returning EWOULDBLOCK the pseudo-device server forces the client process to wait until the pseudo-device becomes writable. 

A signal to the client application can be generated as a side effect by setting sigPtr->signal to a non-zero value.  sigPtr->code can be set to modify the signal.  Data can be accepted by the write service procedure if a signal is generated.  The client application’s write call will complete, its signal handler, if any, will be invoked, and the write call will be retried. 

ioctl

int (∗service->ioctl)(streamPtr, ioctlPtr, selectBitsPtr, sigPtr)
    Pdev_Stream ∗streamPtr;/∗ Set by open service procedure ∗/
    Pdev_IOCParam ∗ioctlPtr;/∗ I/O Control parameter block ∗/
    int ∗selectBitsPtr;/∗ Return - select state of pdev ∗/
    Pdev_Signal ∗sigPtr;/∗ Return - signal to generate, if any ∗/

The ioctl service procedure takes a parameter block that specifies the command, and two buffers, one containing input data (inBuffer), and one for data returned to the client (outBuffer).  The ioctl service has to set ioctlPtr->outBufSize to indicate how much data is being returned to the client process.  The Pdev_IOCParam struct also contains various processIDs, and the format of the host on which the client application is executing. 

The pseudo-device server can implement any ioctlPtr->command it wants.  Generic commands are defined in <fs.h>, and other ranges of commands for particular devices and pseudo-devices are defined in header files in /sprite/src/lib/include/dev. 

The input and output data is not byteswapped by the operating system.  It is the server’s responsibility to fix up the input and output buffers in the case that the client has a different byte order.  The local byte order is defined as MACH_BYTE_ORDER by <machparam.h>, and the client’s byte order and alignment are indicated by ioctlPtr->format.  The Fmt_Convert library routine can be used to swap and align incomming and outgoing buffers. 

A signal to the client application can be generated as a side effect by setting sigPtr->signal to a non-zero value.  sigPtr->code can be set to modify the signal. 

getAttr

int GetAttrProc(streamPtr, attrPtr, selectBitsPtr)
    Pdev_Stream ∗streamPtr;/∗ Identifies service stream ∗/
    Fs_Attributes ∗attrPtr;/∗ Return - attributes ∗/
    int ∗selectBitsPtr;/∗ Return - select state of the pseudo-device ∗/

This procedure is called to handle an fstat() call on a file in a pseudo-file system.  The streamPtr parameter identifies the open stream, and the server should fill in the attributes.  This call-back is not made to regular pseudo-device servers, only to pseudo-file-system servers. 

setAttr

int SetAttrProc(streamPtr, flags, uid, gid, attrPtr, selectBitsPtr)
    Pdev_Stream ∗streamPtr;/∗ Identifies service stream ∗/
    int flags;/∗ Indicate what attributes to set ∗/
    int uid;/∗ Identifies user making the call ∗/
    int gid;/∗ Identifies group of process ∗/
    Fs_Attributes ∗attrPtr;/∗ Attributes to set as indicated by flags ∗/
    int ∗selectBitsPtr;/∗ Return - select state of the pseudo-device ∗/

This procedure is called to set certain attributes of an open file in a pseudo-file system.  The streamPtr parameter identifies the open stream.  The flags argument contains an or’d combinantion of FS_SET_TIMES, FS_SET_MODE, FS_SET_OWNER, FS_SET_FILE_TYPE, FS_SET_DEVICE that indicate what attributes to set.  The attribute values are contained in ∗attrPtr.  The uid and gid arguments identify the calling process.  This call-back is not made to regular pseudo-device servers, only to pseudo-file-system servers. 

Service Stream Ioctls

The pseudo-device server can make a few Fs_IOControl calls on its service streams.  The details of the calling sequences is described in the device man page for pseduo-devices (pdev).  The possible operations are:

IOC_PDEV_READY
Used to change the select state of the pseudo-device.  The input buffer to Fs_IOControl should contain an or’d combination of FS_READABLE, FS_WRITABLE, or FS_EXCEPTION. 

IOC_PDEV_SIGNAL_OWNER
Used to send a signal to the owning process or process group of the pseudo-device.  This is useful for implementing interrupt characters in tty emulators.  No special permission is needed.

IOC_PDEV_WRITE_BEHIND
Used to set or unset asynchronous writing.

IOC_PDEV_BIG_WRITES
Used to allow or disallow writes larger than the request buffer.

IOC_PDEV_SET_PTRS
Used to adjust pointers into the read buffer and the request buffer. Users of the Pdev package should only use this to adjust read buffer pointers.  Leave the request buffer pointers equal to -1 so you don’t mess up the managing of the request buffer.

For example: status = Fs_IOControl(streamPtr->streamID, IOC_PDEV_READY,      sizeof(int), &selectBits, 0, NULL);

SEE ALSO

pdev (devices), Pfs, Swap_Buffer

KEYWORDS

pseudo-device

Sprite version 1.0  —  June 27, 1990

Typewritten Software • bear@typewritten.org • Edmonds, WA 98026