Chapter 13. Services for VME Drivers on Origin 2000/Onyx2

This chapter provides an overview of the kernel services needed by a kernel-level VME device driver on Origin 2000 and Onyx2 systems. The following topics are covered:

Chapter 12, “VME Device Attachment on Origin 2000/Onyx2”, describes the hardware implementation for VME devices. Chapter 4, “User-Level Access to Devices”, covers operation of VME devices from user-level processes.


Note: For information about VME in Challenge and Onyx systems, refer to Chapter 14, “VME Device Attachment on Challenge/Onyx”, and Chapter 15, “Services for VME Drivers on Challenge/Onyx”.


About VME Drivers

A kernel-level VME device driver is an executable module with the structure described in Chapter 7, “Structure of a Kernel-Level Driver”. It uses the driver/kernel interface described in Chapter 8, “Device Driver/Kernel Interface”. In general it is configured into IRIX as described in Chapter 9, “Building and Installing a Driver”.

The general sequence of operations of a VME driver is as follows:

  1. In the pfxinit() entry point, the driver prepares any global variables.

  2. When the kernel processes a VECTOR statement naming this driver (see “Defining VME Devices with the VECTOR Statement” in Chapter 12), it calls the pfxedtinit() entry point of the driver. Here the driver initializes its own per-device data structures, sets up the hardware graph to represent the device, and initializes the device itself, if necessary.

  3. The driver operates the device and transfers data in the normal upper-half entry points such as pfxopen(), pfxread(), pfxwrite(), and pfxstrategy().

These steps are covered in the following topics.

If you are porting a driver from an earlier version of IRIX, the driver will use other functions than the ones described here. See “Porting From IRIX 6.2” for function equivalents. Also, an older driver will not make use of the concept of the hwgraph (see “Hardware Graph” in Chapter 2). It is almost essential to integrate a VME driver for Origin 2000 systems with the hwgraph. This is done during device initialization.

About VME Support Functions

Table 13-1 summarizes in alphabetic order the functions that are unique to the I/O infrastructure for VME. Their uses are mentioned in the following topics. Formal documentation of the functions can be found in the vmeio(D3) reference page.

Table 13-1. Functions of the VME I/O Infrastructure

Function

Purpose

vmeio_dmamap_addr() 

Activate a DMA map and set the target buffer address.

vmeio_dmamap_alloc() 

Allocate a DMA map object.

vmeio_dmamap_done() 

Deactivate a DMA map.

vmeio_dmamap_free() 

Release a DMA map object.

vmeio_dmamap_list() 

Activate a DMA map and set a list of target buffers.

vmeio_intr_alloc() 

Allocate an interrupt object and optionally a VME interrupt vector number.

vmeio_intr_connect() 

Establish an interrupt handler function for a given vector.

vmeio_intr_disconnect() 

Block interrupts to a handler.

vmeio_intr_free() 

Release an interrupt object.

vmeio_intr_vector_get() 

Retrieve the allocated vector number from an interrupt object.

vmeio_piomap_addr() 

Activate a PIO map and get a mapped memory address from it.

vmeio_piomap_alloc() 

Create a PIO map.

vmeio_pio_bcopyin() 

Block-copy PIO data to memory using a PIO map.

vmeio_pio_bcopyout() 

BLock-copy PIO data to the bus, using a PIO map.

vmeio_piomap_done() 

Deactivate a PIO map.

vmeio_piomap_free() 

Release a PIO map.


Initializing the Driver

When the driver has a pfxinit() entry point, that entry point is called during the boot process before any other driver entry point. The driver can initialize global variables at this time. The driver cannot depend on the state of the hardware graph, however. The VME bus attachments may or may not be defined in the hardware graph.

If the driver has a pfxstart() entry point, that entry point is called late in the initialization process, after all device initialization is complete.

For more discussion of these entry points, see “When Initialization Is Performed” in Chapter 7, “Entry Point init()” in Chapter 7, and “Entry Point start()” in Chapter 7.

Initializing a VME Device

The device driver does not know in advance how many devices it will be asked to manage. There might be none configured, or only one, or many. The devices might all be on one VME bus, or they can be on different buses.

The kernel calls the driver's pfxedtinit() entry point once for each VME VECTOR statement it finds (see “Defining VME Devices with the VECTOR Statement” in Chapter 12 for a discussion of the VECTOR statement).

For an overview of the duties and actions of the pfxedtinit() entry point, see “Entry Point edtinit()” in Chapter 7. An important part of initializing the device is setting up the hwgraph. For an overview of hwgraph facilities, see “Hardware Graph Management” in Chapter 8.

In summary, at device initialization time the driver

  • Creates hwgraph vertexes to represent the device

  • Allocates and initializes a data structure to hold per-device information

  • Allocates PIO maps and (optionally) DMA maps to use in addressing the device

  • If necessary, registers an interrupt handler

  • Initializes the device itself

The allocation and use of PIO and DMA maps, and the registration of an interrupt handler, are covered separately in following topics.

Information in the edt_t Structure

The argument to pfxedtinit() is an edt_t structure containing the information listed in Table 13-2. The edt_t structure is declared in the sys/edt.h header file; and the constant values passed in the structure are declared in sys/vme/vmeio.h and sys/vmeregs.h.

The driver is allowed to retain a pointer to the edt_t in memory. A unique copy of the structure is built for each call to pfxedtinit(). The structure is writable and continues to exist after the pfxedtinit() entry point returns.

Table 13-2. VME Driver Contents of edt_t Structure

Field

Contents

e_bus_type 

The constant ADAP_VME.

e_adap 

The VME bus number from VECTOR adapter=b. If the adapter= parameter was omitted, this field contains 0.

e_ctlr 

The value from VECTOR ctrl=c, or 0 if none was given.

e_bus_info 

Pointer to a structure of type vme_intrs_t (declared in sys/vmeregs.h). This structure contains the values from VECTOR ipl=i vector=v.

e_space[] 

Array of three structures of type iospace_t, discussed in a following topic.

e_connectpt 

Handle for the hwgraph vertex representing this device (see “VME Device Naming” in Chapter 12

).

e_master

Handle for the hwgraph vertex representing the VME bus.

e_device_desc 

Pointer to a device descriptor structure containing information about interrupt handling for this device.


Identifying the Bus

If the adapter=b parameter was coded in the VECTOR statement, e_adap contains the number. When e_adap is zero, the bus number is unspecified at this time. The driver is being called early in the startup process, before ioconfig has assigned sequential bus numbers (see “Using the adapter=b Parameter” in Chapter 12).

In any event, the handle in e_master is unique to the bus. If you need to compare two devices to see if they are on the same bus, you can compare their e_master values.

Using the Controller Number

The number in e_ctlr comes from the ctrl=c parameter of the VECTOR statement, when used. This number has no hardware significance; it is a numeric parameter to the driver that you can use for any purpose. Typically it is used as a way of giving each device a unique number that can be displayed, for example, in error messages.

If you want to pass more than a few bits of information to the driver, consider using the DEVICE_ADMIN and DRIVER_ADMIN statements (see “Retrieving Administrator Attributes” in Chapter 8).

Using the iospace List

Each iospace_t value in the e_space array describes one iospace parameter given in the VECTOR statement. Normally at least one such value is given. If none are given, the address space, base, and size values must be hard-coded into the driver—not usually a good plan.

The values in each iospace_t structure are listed in Table 13-3.

Table 13-3. VME Driver Contents of iospace_t Structures

Field

Contents

ios_type 

Address space constant declared in vmeio.h such as VMEIO_SPACE_A16N. VMEIO_SPACE_NONE appears for an unused structure.

ios_iopaddr 

Base address from VECTOR statement.

ios_size 

Size of address range from VECTOR statement.

ios_vaddr 

Not initialized, but available for driver use.


Verifying Device Addresses

When no probe is coded in the VECTOR statement, you have no assurance that the device exists (see “Using the exprobe Parameter” in Chapter 12). When a driver attempts to access a nonexistent VME address, a bus error results, causing a kernel panic. Accordingly it is an excellent idea to test at least the lowest and highest PIO addresses in each iospace using the functions summarized under “Testing Device Physical Addresses” in Chapter 8.

Setting Up the Hardware Graph

The handle in e_connectpt represents the hwgraph vertex built by the kernel for this VECTOR statement. The driver needs to create another vertex attached to this one with a meaningful product-related name. That is the vertex that programs will open; it is also the vertex in which the driver can store its per-device information.


Note: Under IRIX 6.4 only, depending on the system patch level, it may be possible for the pfxedtinit() entry point to be called when no device exists. The sign that this has happened is that e_connectpt contains 0. For IRIX 6.4 only you may need to place test such as the following near the beginning of device initialization:


if (!(edt->e_connectpt)) return; /* invalid call */

For an overview of hardware graph modification see “Hardware Graph Management” in Chapter 8, and in particular “Extending the Graph With a Single Vertex” in Chapter 8. Using the example devices under “Example VME Configuration” in Chapter 12, the kernel creates the connection point

/hw/module/mod/slot/ion/baseio/vme_xtown/pci/7/vmebus/a32n/1008000

This same vertex is also visible to the user with the convenience path

/hw/vme/1/a32n/1008000

However, this vertex is not a device vertex and the user cannot open it. Your driver needs to create a device vertex for this purpose.

You know that the device is in fact a character device, a Frummage Corp model AD6. You want to add a single character device vertex named ad6. In that vertex you can store a pointer to a device information structure you call ad6_stuff_t. This is the vertex that is passed to entry points such as pfxread(), and the device information will be available at those times.

The code in Example 13-1 is a hypothetical (i.e. untested) fragment that might appear in your pfxedtinit() entry point, somewhere after the point at which you know the device exists.

Example 13-1. Adding a Vertex to the Hardware Graph

ad6_stuff_t * ad6;
vertex_hdl_t vert;
...
   /* allocate memory for per-device structure */
   ad6 = kmem_zalloc(sizeof(*ad6),KM_SLEEP);  
   if (!ad6) goto SayDie;
   /* create device vertex below connect-point */ 
   ret = hwgraph_char_device_add(edt->e_connectpt,
            "ad6", "ad6_", &vert);
   if (ret != GRAPH_SUCCESS) goto SayDie;  
...
   /* set info struct in the device vertex */
   device_info_set(vert,ad6); ...
SayDie:
   /* release all allocated objects and exit */

Following this step, the new vertex is visible in the hwgraph under the two paths:

/hw/module/mod/slot/ion/baseio/vme_xtown/pci/7/vmebus/a32n/1008000/ad6
/hw/vme/1/a32n/1008000/ad6

Note that you did not have to worry about uniqueness, since there can be only one path to this particular bus, address space, and base address. However, even the shorter of these paths is cumbersome. You would like to create an alias that is even shorter, for example, /hw/Frummage/ad6. However, you cannot be sure that there is only this one AD6 board in the system. There might be several, and if there are, you cannot predict the order in which they are initialized. Initialization calls might even occur in parallel on different CPUs.

What you need to create is a path /hw/Frummage/n/ad6, where n is certain to be unique among all Frummage devices. The simplest way to create a unique numbering is through the ctlr=c parameter of the VECTOR statement.

The steps to create this convenience path would include:

  • Use hwgraph_traverse() to test if /hw/Frummage has been created yet.

  • If it has not, create a new vertex using hwgraph_vertex_create(), and connect it to the root of the hwgraph by an edge “Frummage” using hwgraph_edge_add().

  • Convert the controller number (or a unique integer from some other source) into a character string “n” for use as a unique edge.

  • Connect an edge, named with the unique numeric string, between the /hw/Frummage vertex and the newly-created ad6 vertex, using hwgraph_edge_add(). (If this fails, it is probably because the number is not unique after all and the edge already exists.)

Dealing with Initialization Errors

The initialization process can encounter many possible errors, for example:

  • No iospace information in the edt_t.

  • Unable to allocate a PIO map, a DMA map, or some other data structure.

  • Unable to verify a device address (device does not exist).

  • Unable to create a hwgraph vertex or edge.

You do not expect these errors to occur and usually they will not, but when one inevitably does occur you want the driver to behave sensibly and to be informative. Plan in advance for this; there are a variety of coding techniques you can use.

Creating and Using PIO Maps

A PIO map is a system object that represents a range of addresses in one VME address space. After creating a PIO map, a device driver can use it in the following ways:

  • Obtain mapped addresses in kernel virtual address space that represent the device. These can be used to load or store data words between the CPU and the device, or they can be mapped into user process space for loading or storing.

  • Copy data between the device and memory, or perform compare-and-swap to the device, without learning the specific kernel addresses involved.

The kernel virtual address returned by PIO mapping is not a physical memory address, and it bears no relationship to the VME bus address. The kernel virtual address and the VME bus address need not have any bits in common.

The functions used with PIO maps are summarized in Table 13-4.The syntax of these functions is given in the vmeio(D3) reference page.

Table 13-4. Functions to Create and Use PIO Maps

Function

Purpose

vmeio_piomap_addr() 

Get a mapped memory address for a device address.

vmeio_piomap_alloc() 

Create a PIO map.

vmeio_piomap_done() 

Deactivate a PIO map.

vmeio_piomap_free() 

Release a PIO map.

vmeio_pio_bcopyin() 

Block-copy PIO data to memory using a PIO map.

vmeio_pio_bcopyout() 

Block-copy PIO data to the bus, using a PIO map.

The expected sequence of use is:

  • During device initialization, allocate a PIO map for the device.

  • To use a map (during initialization or at any other entry point), get the base address of the device using vmeio_piomap_addr(). Use the returned address for PIO, or request block copy operations.

  • When finished accessing device addresses at any one entry point, ensure completion of all PIO operations by calling vmeio_piomap_done().

  • When shutting down the device or system, release the map with vmeio_piomap_free().

Allocating and Freeing PIO Maps

A driver creates a PIO map by calling vmeio_piomap_alloc(). This function performs memory allocation and it associates the PIO map with a “VME slave image” in the VME controller. Either of these operations can encounter a delay. You specify whether the function should return an error when a resource is not available, or should wait.

Typically your driver creates its PIO map in the pfxedtinit() entry point, where the driver first learns about the device addresses from the contents of the edt_t structure. At this time a delay is usually acceptable. Unless the device registers are scattered widely in its address space, one PIO map is usually sufficient.

The parameters to vmeio_piomap_alloc() are as follows:

vme_conn 

Hwgraph connection point of the device, as passed in the e_connpt field of edt_t (see “Information in the edt_t Structure”

).

dev_desc 

Current device descriptor for the device. This can be obtained from the e_device_desc field of the edt_t, or by calling device_desc_default_get() with the connection point.

am 

Constants specifying the VME address space modifier, discussed below.

vmeaddr 

Base address of a range of VME bus addresses, typically the ios_iopaddr field of an iospace_t structure (see “Using the iospace List”

).

byte_count 

Size of the address range to be mapped, typically the ios_size field of an iospace_t structure.

byte_count_max 

Maximum size to be presented to vmeio_piomap_addr() at any time.

flags 

Zero (0) or VME_DEBUG depending on whether integrity checking should be done, with a slight performance penalty.

When the VMEIO_NOSLEEP flag is used, it is possible for the function to fail and return a NULL pointer. In this case, the PIO map could not be allocated.

Typically a driver creates a PIO map and stores its address in a structure with other information about one VME device. A pointer to that structure is stored in the device vertex in the hwgraph (see “Setting Up the Hardware Graph”). In any entry point, the address of the device structure, and the PIO map address, are easily retrieved.

Specifying the Address Space and Modifiers

Any PIO map operates in one VME address space using one address modifier. You specify these with a combination of constant names as the am parameter to vmeio_piomap_alloc(). The allowed combinations are listed Table 13-5. Other combinations of space and modifier are not supported.

Table 13-5. Address Space and Modifiers Available for PIO

Address Modifier

Value of am Parameter

Description

0x29

VMEIO_AM_A16 + VMEIO_AM_N + (either VMEIO_AM_D8 or VMEIO_AM_D16)

A16 nonprivileged

0x2D

VMEIO_AM_A16 + VMEIO_AM_S + (either VMEIO_AM_D8 or VMEIO_AM_D16)

A16 supervisory

0x39

VMEIO_AM_A24 + VMEIO_AM_N + (one of VMEIO_AM_D8, VMEIO_AM_D16, VMEIO_AM_D32)

A24 nonprivileged

0x3D

VMEIO_AM_A24 + VMEIO_AM_S+ (one of VMEIO_AM_D8, VMEIO_AM_D16, VMEIO_AM_D32)

A24 supervisory

0x09

VMEIO_AM_A32 + VMEIO_AM_N + VMEIO_AM_SINGLE + VMEIO_AM_D32

A32 nonprivileged 32-bit data

0x0B

VMEIO_AM_A32 + VMEIO_AM_N + VMEIO_AM_BLOCK + VMEIO_AM_D32

A32 nonprivileged32-bit block data

0x08

VMEIO_AM_A32 + VMEIO_AM_N + VMEIO_AM_BLOCK + VMEIO_AM_D64

A32 nonprivileged 64-bit block

0x0D

VMEIO_AM_A32 + VMEIO_AM_S + VMEIO_AM_SINGLE + VMEIO_AM_D32

A32 supervisory 32-bit data

0x0F

VMEIO_AM_A32 + VMEIO_AM_S + VMEIO_AM_BLOCK + VMEIO_AM_D32

A32 supervisory 32-bit block data

0x0C

VMEIO_AM_A32 + VMEIO_AM_S + VMEIO_AM_BLOCK + VMEIO_AM_D64

A32 supervisory 64-bit block data


Testing the Target Addresses

Merely creating a map does not guarantee that any device exists at that bus address. Device addresses passed in the VECTOR statement can only be trusted if the VECTOR statement also contains a thorough set of exprobe= commands to probe the bus.

In order to test the validity of a VME bus address, you must create a PIO map and test the mapped addresses using one of the functions listed under “Testing Device Physical Addresses” in Chapter 8. The following is the recommended sequence of operations when initializing a device:

  • Allocate a PIO map.

  • Activate the PIO map, retrieving the base address for the device.

  • Test the valid low and high extremes of the device address range using a function such as badaddr().

  • If an address is not valid the device does not exist or is not the type of device you expected. In this case:

  • When the extreme addresses test valid, continue to initialize the device and complete initialization with a call to vmeio_piomap_done().

Freeing a PIO Map

As long as the PIO map object is safely anchored from the device vertex, there is no need to release the object. A loadable driver can unload and reload, the map is still available. If a PIO map is created on the fly, it can be released with a call to vmeio_piomap_free().

Using a PIO Map for PIO

You get a kernel virtual address from a map for use with PIO by applying vmeio_piomap_addr(). In this function call you specify the lowest VME address of interest at this time, and the total range of VME addresses that you want to access at this time. Typically these two numbers are the same as the vmeaddr and byte_count parameters to vmeio_piomap_alloc().

The value returned by vmeio_piomap_addr() is an address in kernel virtual space that is associated with the requested base address on the VME bus. Stores and fetches to the mapped address range are executed on the VME bus.

When the driver does not need further PIO for a period of time that might be long, the driver should call vmeio_piomap_done(). This call does not return until all PIO output has reached the device. In some systems it may release temporary resources.

Mapping a PIO Map into a User Process

In the pfxmap() entry point (see “Concepts and Use of mmap()” in Chapter 7), you can also use a mapped PIO address with the v_mapphys() function to map the range of device addresses into the address space of a user process. When you do this, the PIO map remains in use for an indefinite time. You can release the PIO map in the pfxunmap() entry point (see “Entry Point unmap()” in Chapter 7).

Using a PIO Map for Block Copy

The vmeio_pio_bcopyin() and vmeio_pio_bcopyout() functions copy a range of data between memory and a device using a PIO map. These functions are optimized to the hardware that exists, and they do all transfers in the largest size possible (32 or 64 bits per transfer). If you need to transfer data in specific sizes of 1 or 2 bytes, create a PIO map for that width and use direct loads and stores to the mapped addresses.

Creating and Using DMA Maps

A DMA map is a system object that can represent a mapping between a buffer in kernel virtual space and a range of VME bus addresses. After creating a DMA map, a driver uses the map to specify the target address and length to be programmed into a VME bus master before a DMA transfer.

The functions that operate on DMA maps are summarized in Table 13-6.

Table 13-6. Functions That Operate on DMA Maps

Function

Purpose

vmeio_dmamap_addr() 

Activate a DMA map and set the target buffer address.

vmeio_dmamap_alloc() 

Allocate a DMA map object.

vmeio_dmamap_done() 

Deactivate a DMA map.

vmeio_dmamap_free() 

Release a DMA map object.

vmeio_dmamap_list() 

Activate a DMA map and set a list of target buffers.


Allocating a DMA Map

A device driver allocates a DMA map using vmeio_dmamap_alloc(). This is typically done in the pfxedtinit() entry point, provided that the maximum I/O size is known at that time. The arguments to this function are as follows:

vme_conn 

Hwgraph connection point of the device, as passed in the e_connpt field of edt_t (see “Information in the edt_t Structure”

).

dev_desc 

Current device descriptor for the device. This can be obtained from the e_device_desc field of the edt_t, or by calling device_desc_default_get() with the connection point.

space 

Constant specifying the address space, discussed below.

byte_count_max 

Largest range of addresses to be mapped at any time.

flags 

Zero (0) or VME_DEBUG depending on whether integrity checking should be done, and VMEIO_DMA_CMD or VMEIO_DMA_DATA to describe the use of the map.


Specifying the Type of Access

The VME support code assumes that a DMA map is used for one of two kinds of access:

  • Command-type access in which relatively small quantities of command or status information are exchanged between the device and word-aligned buffers.

    Command-type access is characterized by short, infrequent transfers that are not necessarily aligned on a cache line boundary. For example, if the device can load its own scatter/gather register set using DMA, or if the device uses DMA to store a block of status information, this is command-type DMA access.

  • Data-type access in which relatively large amounts of data are exchanged between the device and buffers that are at least a cache-line in length and typically aligned to cache boundaries.

    Data-type access is characterized by long, multiple-cache-line bursts of data.

You state the type of DMA access by specifying either VMEIO_DMA_CMD or VMEIO_DMA_DATA in the flags parameter when allocating the map. When the kernel schedules I/O through a data-type DMA map, it sets up the XIO and PCI interfaces for best bandwidth under the assumption that multiple cache lines will be transferred. When the kernel schedules I/O through a command-type DMA map, it sets up the interfaces for quickest coherent access to data.

Specifying the Address Space

Any DMA map operates in one VME address space. You specify the space with a value in the space parameter to vmeio_dmamap_alloc(). The available spaces are shown in Table 13-7.

Table 13-7. Address Space and Modifiers Available for DMA

Constant in vmeio.h

Description

VMEIO_AM_A24 + VMEIO_AM_N

A24 nonprivileged

VMEIO_AM_A24 + VMEIO_AM_S

A24 supervisory

VMEIO_AM_A32 + VMEIO_AM_N

A32 nonprivileged data

VMEIO_AM_A32 + VMEIO_AM_S

A32 supervisory data

The data width is determined by the VME bus master device when it initiates the bus transactions.

Using a DMA Map for One Buffer

A DMA map is used prior to a DMA transfer into or out of a buffer in kernel virtual space. The function vmeio_dmamap_addr() takes a DMA map, a buffer address, and a length. It assigns a span of contiguous VME addresses of the specified length. It programs the VME bus controller to map that range of VME addresses into the physical addresses that represent the specified buffer. The buffer may span two or more physical pages.

The value returned by vmeio_dmamap_addr() is the VME bus virtual address that represents the first byte of the buffer. This is the address you program into the bus master device (using a PIO store), in order to set its starting transfer address. Then you can initiate the DMA transfer (again by storing a command into a device register using PIO).

It is possible for vmeio_dmamap_addr() to fail. The kernel needs to allocate resources in the VME controller in order to set up and maintain the mapping. If the resources are not available, the function returns 0. You must always test the returned value for zero and not attempt the DMA operation when that value appears.

Using a DMA Map with Address/Length Lists

IRIX now supports the very convenient and useful address/length list (alenlist) functions described under “Using Address/Length Lists” in Chapter 8. You can use an alenlist to represent a series of one or more buffer areas separated in kernel virtual memory. In particular, the buf_to_alenlist() function returns an alenlist describing a kernel memory buffer.

When your buffer is defined by an alenlist, you can map all components in the list in a single operation by calling vmeio_dmamap_list(). This function creates an alenlist in which the same segments of memory are represented as a list of VME bus addresses. (Note that the new list can have more or fewer address/length pairs than the original list.) The function returns the list containing translated addresses. This is a newly allocated alenlist object unless you specify VMEIO_INPLACE in the flags argument.

You can read out VME bus address segments from the list one at a time and program them into the device for DMA. If the device has multiple target registers (“scatter/gather” registers) you can program it with all the segments from the translated list. If the device can handle only one transfer at a time, you have to program each list element as a separate operation.

Handling VME Interrupts

When a VME interrupt occurs, the VME bus controller directs the interrupt signal to one of the CPUs in the Origin 2000 system (see “Directing VME Interrupts” in Chapter 12).

The kernel VME support in that CPU fetches the interrupt vector presented during the interrupt acknowledge (IACK) cycle on the VME bus. Then the kernel looks to see if any VME driver has asked to receive interrupts with that vector on that priority level. If a driver has registered a handler for this interrupt, the kernel schedules the driver's handler to execute asynchronously as a kernel thread. This phase of acknowledgment and scheduling runs as a trap handler in the CPU that receives the hardware signal. The driver's handler function can execute in that same CPU (by default) or in another one.

In order to receive control on an interrupt, you must consider these issues:

  • Establish the VME interrupt priority level the device uses.

  • Establish the VME interrupt vector the device presents during an interrupt acknowledge cycle.

  • Create an interrupt handler function to be called when the device interrupts.

  • Notify the kernel to call the handler when the interrupt occurs.

Connecting the Interrupt Handler

You establish an interrupt handler by creating and using an interrupt object. This dynamic method of connecting a handler has the following advantages:

  • You decide when interrupts will be accepted.

  • You can disconnect the handler when interrupts are not wanted (for example, when the driver needs to unload).

  • You can allocate an interrupt vector dynamically, for programming into a device that has a programmable vector number.

The functions used for interrupt control are summarized in Table 13-8.

Table 13-8. Functions for Interrupt Control

Function

Purpose

vmeio_intr_alloc() 

Allocate an interrupt object, and optionally allocate a vector number.

vmeio_intr_vector_get() 

Retrieve the vector number specified to, or allocated by vmeio_intr_alloc().

vmeio_intr_connect() 

Establish an interrupt handler and enable interrupts.

vmeio_intr_disconnect() 

Block interrupts so that the handler is no longer called.

vmeio_intr_free() 

Release an interrupt object.


Allocating an Interrupt Object

During initialization of the device in the edtinit() entry point, allocate an object to represent the expected interrupt. This is the purpose of vmeio_intr_alloc(). The parameters to this function are as follows:

vme_conn 

Hwgraph connection point of the device, as passed in the e_connpt field of the edt_t (see “Information in the edt_t Structure”

).

dev_desc 

Current device descriptor for the device. This can be obtained from the e_device_desc field of the edt_t, or by calling device_desc_default_get() with the connection point.

vec 

Interrupt vector value, or VMEIO_INTR_VECTOR_NONE to request allocation of an unused vector.

level 

Interrupt priority level used by the device, typically passed indirectly through the e_bus_info field of the edt_t structure.

owner_dev 

Any hwgraph vertex that the system should display when reporting an error in interrupt handling. Typically the device vertex created by the driver.

flags 

Zero (0) or VME_DEBUG depending on whether integrity checking should be done, with a slight performance penalty.

The priority level and vector number can be passed in the VECTOR statement (see “Information in the edt_t Structure”), or they can be coded into the driver. When the device can be programmed with a vector number, you can request the assignment of an unused vector number by passing VMEIO_INTR_VECTOR_NONE. You can retrieve the assigned (or specified) vector number by calling vmeio_intr_vector_get(), then program it into the device by PIO.


Note: According to the VME standard, the interrupt vector can be a data item of 8, 16, or 32 bits. However, Silicon Graphics systems accept only an 8-bit vector, and its value must fall in the range 1-254 inclusive. (0x00 and 0xFF are excluded because they could be generated by a hardware fault.)

Typically you should store the address of the interrupt object in the device information structure for future use.

Using the Device Descriptor

The use of the device_desc data type is described in two reference pages, device_desc(d4x) and device_desc_ops(d3x) . Where a device descriptor is required, you can obtain a default descriptor handle by calling device_desc_default_get(). However, you can also obtain a writable copy of the device descriptor and modify it with a function such as device_desc_intr_target_set().

The device descriptor that you pass to vmeio_intr_alloc() determines the CPU on which your interrupt handler will execute. Normally the default interrupt CPU is appropriate. If you have a good reason to know that the handler should execute on a particular CPU, you can modify the device descriptor before calling vmeio_intr_alloc().

Connecting the Handler

Once you have created an interrupt object, you can enable calls to your handler function by calling vmeio_intr_connect(). The important parameters to this function are:

  • The interrupt object, which indirectly specifies the device, level, and vector.

  • The address of the handler function, which can have any name you choose.

  • An argument to be passed to the function, typically the handle of the device information structure you have prepared.

Although the function also takes a “thread” parameter, there is no current support for operating the interrupt handler on a specific kernel thread. It is called at a high priority, but not necessarily the highest priority (see “Interrupts as Threads” in Chapter 7).

Disconnecting the Handler

When you want to ensure that the interrupt handler will not be called, apply vmeio_intr_disconnect(). Following interrupts from the device are discarded until you once again call vmeio_intr_connect().

You must disconnect interrupts in a loadable driver before unloading the driver.

Porting From IRIX 6.2

The kernel services for VME programming changed substantially for Origin and Onyx2 systems. This section lists the Challenge and Onyx kernel functions and relates them to the Origin and Onyx2 interface as it is described in the preceding topics. Refer to Chapter 14, “VME Device Attachment on Challenge/Onyx”, and Chapter 15, “Services for VME Drivers on Challenge/Onyx” for information on writing VME device drivers for Challenge and Onyx systems.

Table 13-9 lists the kernel functions used under IRIX 5.3, IRIX 6.0, IRIX 6.1, and IRIX 6.2 to program the VME interface on the Challenge and Onyx systems, in alphabetical order.After the name of the function is a brief summary of its purpose; in the third column is a summary of the comparable facility introduced in IRIX 6.5.

When you begin porting a VME driver from IRIX 6.2 or earlier, look each function up in Table 13-9. Rewrite the driver to use the function or functions listed in the rightmost column.

Table 13-9. VME Kernel Function Compatibility Summary

Old Function

Purpose

Replacement Facility

dma_map()

Map a buffer for DMA.

vmeio_dmamap_addr() and vmeio_dmamap_list()

dma_mapaddr()

Get the VME bus address for a mapped buffer.

vmeio_dmamap_addr() and vmeio_dmamap_list()

dma_mapalloc()

Allocate a DMA map.

vmeio_dmamap_alloc()

dma_mapbp()

Map a system buffer for DMA.

buf_to_alenlist(), then vmeio_dmamap_list()

dma_mapfree()

Release a DMA map.

vmeio_dmamap_free()

pio_and[bhw]_rmw()

Perform read-AND-writeback.

No replacement

pio_bcopyin()

Block-copy PIO data to memory.

vmeio_pio_bcopyin()

pio_bcopyout()

Block-copy PIO data to device.

vmeio_pio_bcopyout()

pio_mapaddr()

Position PIO map in VME address space.

vmeio_piomap_addr()

pio_mapalloc()

Allocate a PIO map.

vmeio_piomap_alloc()

pio_mapfree()

Release a PIO map.

vmeio_piomap_free()

pio_or[bhw]_rmw()

Perform read-OR-writeback.

No replacement

vme_adapter()

Get number of VME bus for device.

No replacement

vme_ivec_alloc()

Reserve unused VME vector number.

vmeio_intr_alloc()

vme_ivec_free()

Release VME vector number.

vmeio_intr_free()

vme_ivec_set()

Register interrupt handler for VME vector.

vmeio_intr_connect()


Sample VME Device Driver

In Example 13-2 you can read the code of a complete VME driver provided courtesy of the VME Microsystems International Corporation (VMIC). The code of this driver may also be installed as part of VME support in the directory /usr/src/vme. Sample driver code may also be made available from SGI Developer Support.

The sample driver has been set up for conditional compilation on either IRIX 6.2 or IRIX 6.4. The statements

jjjjj#if SN 
#define VMEIO   1
#endif

set up the value of the VMEIO compiler variable. This variable is used to select code for either the older IRIX 6.2 or the newer IRIX 6.5.

Example 13-2. Sample VME Driver

/*
 * SGI would like to thank VME Microsystems International Corporation
 * (VMIC) for providing source code to the device driver for their
 * VMIVME-5588DMA Reflective Memory Card. Reflective memory is a
 * real-time network that supports low latency data transfers among
 * heterogenous systems via either PCI or VME.  Data written to a
 * reflective memory board location is atomatically sent to all
 * reflective memory boards in the network.  This is all done by the
 * Reflective Memory hardware. No software messaging or CPU cycles are
 * required.  For more information, contact VMIC at:
 * 
 * VME Microsystems Int'l Corp
 * 12090 South Memorial Parkway
 * Huntsville AL 35803 USA
 * (800) 322-3616
 * http://www.vmic.com
 *--------------------------------------------------------------------------
 *                       COPYRIGHT NOTICE
 *
 *       Copyright (C) 1996 VME Microsystems International Corporation
 *       International copyright secured.  All rights reserved.
 *--------------------------------------------------------------------------
 *      VMIVME/SW-5550-ABC-205 Device Driver for IRIX-6.4
 *--------------------------------------------------------------------------
 * FYI: All non-static symbols must, must, *MUST*, begin with "rfm_".
 *--------------------------------------------------------------------------
 */
#ifndef lint
static char rfm_c_sccs_id[] = "@(#)rfm.c 1.60 96/07/23 VMIC";
#endif  /* lint */
#include <sys/types.h>
#include <sys/debug.h>
#include <sys/param.h>
#include <sys/immu.h>
#include <ksys/ddmap.h>
#include <sys/conf.h>
#include <sys/edt.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/ksynch.h>         /* For SV_ALLOC(D3DK), et. al.           */
#include <sys/sema.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <sys/mload.h>
#include <sys/buf.h>
#include <sys/dir.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/sg.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/vmereg.h> 
#include <stdarg.h>
#if SN
#define VMEIO   1
#endif
#if VMEIO
#include <sys/ioerror.h>
#include <sys/alenlist.h>
#include <sys/vme/vmeio.h>
#else /* Challenge */
#include <sys/pio.h>
#include <sys/dmamap.h>
#include <sys/user.h>          /* The header file is gone from 6.4 on */
#endif 
#include <rfm_reg.h>
#include <rfm_io.h>
#include "spaces.h"
#include "bflags.h"
#if VMEIO
#define RFM_PREFIX      "rfm_"
#define EDGE_LBL_RFM    "rfm" 
#define NEW(ptr)        (ptr = kmem_alloc(sizeof (*(ptr)), KM_SLEEP))
#define DEL(ptr)        (kmem_free(ptr, sizeof (*(ptr))))
#endif
#if VMEIO
typedef vmeio_piomap_t  rfm_piomap_t;
#else
typedef piomap_t       *rfm_piomap_t;
#endif 
#if VMEIO
typedef vmeio_dmamap_t  rfm_dmamap_t;
#else
typedef dmamap_t       *rfm_dmamap_t;
#endif 
#if VMEIO
/* vmeio_dmamap_alloc returns "0" for errors */
#define DMAMAP_FAILED   ((rfm_dmamap_t) 0L)
#else
/* dmamap_alloc returns "-1" for errors */
#define DMAMAP_FAILED   ((rfm_dmamap_t) -1L)
#endif
/*
 *========================================================================
 * Configuraion constants (you may change these, if needed)
 *========================================================================
 */
/*
 *------------------------------------------------------------------------
 * NRFM is the number of devices which we will support.  Making this
 * too big uses only a little extra memory.
#if VMEIO
 * When usint VMEIO, NRFM is ignored.
#endif
 *------------------------------------------------------------------------
 */
#ifndef NRFM
#define NRFM    2                       /* Support 2 boards              */
#endif  /* NRFM */
/*
 *------------------------------------------------------------------------
 * EVENTPATIENCE is the default number of seconds to which an event
 * timeout is limited.
 *------------------------------------------------------------------------
 */
#ifndef EVENTPATIENCE
#define EVENTPATIENCE   20              /* Timeout limit (seconds)       */
#endif  /* EVENTPATIENCE */
/*
 *------------------------------------------------------------------------
 * RFMSLEEP is the priority level at which the driver will sleep during
 * timeouts. If you don't want a signal (such as happen when you type a ^C
 * on the keyboard) from interrupting the wait, then uncomment the
 * definition below *without* "PCATCH". However, if you don't allow
 * signals to interrupt the wait and the event never happens, you'll have
 * to reboot the system in order to kill your applications program.
 *------------------------------------------------------------------------
 */
#if     1
#define RFMSLEEP        (PCATCH|(PZERO+1))      /* ^C interrupts wait    */
#else   /* NOPE */
#define RFMSLEEP        (PZERO-1)               /* Nothing interrupts    */
#endif  /* NOPE */
/*
 *------------------------------------------------------------------------
 * The SMALLEST_DMA symbol gives the size (in bytes) of the smallest DMA
 * transfer we will perform. Any attempted read(2)'s or write(2)'s shorter
 * than this will be handled by programmed I/O (copying the data via the
 * CPU). The rationale is this: the DMA setup, interrupt processing, and
 * DMA teardown takes a certain amount of time. For shorter transfers this
 * overhead is greater than the time it would take to just move the data
 * using the CPU. You may want to experiment with this value to determine
 * the optimal limit for your system.
 *
 * The SMALLEST_DMA_IREQ symbol gives the size (in bytes)of the smallest
 * DMA which will use a DMA complete interrupt; transfers smaller than
 * this will simply poll the DMA engine's complete flag. You may want to
 * experiment with this value to determine the optimal limit for your
 * system. In view of the relatively large overhead incurred in processing
 * any interrupt, you may want this value to be fairly high so that you
 * reduce the transfer latency (although this might saturate the CPU).
 * Your mileage may vary :-)
 *------------------------------------------------------------------------
 */
#define SMALLEST_DMA            (1024)  /* Smallest DMA we will do       */
#define SMALLEST_DMA_IREQ       (2048)  /* Smallest DMA using interrupt  */
/*
 *------------------------------------------------------------------------
 * The following data structure defines the default DMA configuration
 * options used unless the application program uses an RFM_DMAINFO
 * ioctl(2) command to change them.
 *------------------------------------------------------------------------
 */
static rfmdmainfo_t     defaultDmaInfo =        {
        1,                              /* U-seconds between DMA polls   */
        64,                             /* DMA burst cycle length (0=64) */
        RDI_RELMODE_ROR,                /* DMA release mode              */
        0,                              /* Burst interleave (x250 nsec)  */
        3,                              /* VMEbus request level          */
        SMALLEST_DMA,                   /* Minimum size to use DMA       */
        SMALLEST_DMA_IREQ               /* Minimum DMA w/interrupt size  */
};
/*
 *========================================================================
 * PLEASE DO NOT ALTER ANYTHING PAST THIS POINT
 *========================================================================
 */
/* IRIX 6.2 has some missing prototypes                                  */
void MUTEX_LOCK(mutex_t *lockp, int priority);
void SV_WAIT( sv_t *svp, void *lkp, int rv );
int badaddr(volatile void *addr, int size);/* In "systm.h", can't use it */
/* Use our own min/max macros, not whatever happens to be lying around   */
#undef  min
#undef  max
#undef  bounds
#define max(a,b)        ( (a) > (b) ? (a) : (b) )
#define min(a,b)        ( (a) < (b) ? (a) : (b) )
/*
 *------------------------------------------------------------------------
 * Number of bytes transferred with each DMA cycle
 *------------------------------------------------------------------------
 */
#define DMAWIDTH        8               /* Must be power of two!         */
/*
 *------------------------------------------------------------------------
 * We encode the size of the reflective memory device in the low-order
 * bytes of the minor device number.
 *------------------------------------------------------------------------
 */
#define RFMUNIT(dev)    getminor(dev)
/*
 *------------------------------------------------------------------------
 * Macros to win friends and influence people
 *------------------------------------------------------------------------
 */
#define USECONDS        ((clock_t) 1000000UL)   /* Usec per second       */
#define WHENDEBUG(x)    if( (x) & rfmDebug )
static uint_t rfmDebug = RFM_DBERROR;   /* Debug output level    */
#if 0
static uint_t rfmDebug = RFM_DBINIT | RFM_DBOPEN | RFM_DBCLOSE | RFM_DBREAD | 
                         RFM_DBWRITE | RFM_DBSTRAT | RFM_DBIOCTL | RFM_DBMMAP |
                         RFM_DBCALLBACK | RFM_DBTIMER | RFM_DBINTR | 
                         RFM_DBERROR | RFM_DBSLOW | RFM_DBMUTEX;
#endif
/*
 *------------------------------------------------------------------------
 * Interrupt enable flags for all four interrupts. We setup the BIM to
 * enable all interrupts and to automatically clear the interrupt when the
 * interrupt occurs. We also the the BIM's "FLAG" as an indication that
 * the associated interrupt occurred.  We need this because we will have
 * to poll the device, since IRIX 5.X may share interrupts among VME
 * devices.
 *------------------------------------------------------------------------
 */
#define A_ENABLE        (RFM_BIM_IRE | RFM_BIM_AUTO | RFM_BIM_F | RFM_BIM_FAC)
#define B_ENABLE        (RFM_BIM_IRE | RFM_BIM_AUTO | RFM_BIM_F | RFM_BIM_FAC)
#define C_ENABLE        (RFM_BIM_IRE | RFM_BIM_AUTO | RFM_BIM_F | RFM_BIM_FAC)
#define F_ENABLE        (RFM_BIM_IRE | RFM_BIM_AUTO | RFM_BIM_F | RFM_BIM_FAC)
#define D_ENABLE        (RFM_BIM_IRE | RFM_BIM_AUTO | RFM_BIM_F | RFM_BIM_FAC)
/*
 *------------------------------------------------------------------------
 * Multiprocessor access to the top half of the driver routines (like
 * rfmopen(), rfmread(), and the like) are coordinated by a mutual
 * exclusion (mutex) lock. The lock is initialized while booting, so
 * everyone can use it.  Because all of the top-level (i. e. system call
 * routines) are mutually exclusive, we can allow multiple user-level open
 * calls without worrying about access collisions.
 *------------------------------------------------------------------------
 */
#define TOPHALF_LOCK(ucb)       MUTEX_LOCK( &(ucb)->ucb_mutex, (-1) )
#define TOPHALF_UNLOCK(ucb)     MUTEX_UNLOCK( &(ucb)->ucb_mutex )
/*
 *------------------------------------------------------------------------
 * Coordination between the bottom (interrupt level) half of the driver
 * and the upper (base level) half is coordinated by a lock and a
 * synchronization variable. Since all of the upper halves use a mutex,
 * there is only one active upper level context active. By gating the
 * interrupt handler with the synchronization variable, we achieve
 * interrupt lockout of the upper half.
 *------------------------------------------------------------------------
 */
#define RFM_LOCK_INIT(ucb)      LOCK_INIT( &(ucb)->ucb_rfmLock, 0, 0, 0)
#define RFM_LOCK(ucb,level)     LOCK( &(ucb)->ucb_rfmLock, (level) )
#define RFM_UNLOCK(ucb,cookie)  UNLOCK( &(ucb)->ucb_rfmLock, (cookie) )
#define RFM_WAIT(ucb,cookie)    SV_WAIT( &(ucb)->ucb_rfmSv, \
                                        &(ucb)->ucb_rfmLock, (cookie) )
#define RFM_TELLONE(ucb)        SV_SIGNAL( &(ucb)->ucb_rfmSv )
#define RFM_TELLALL(ucb)        SV_BROADCAST( &(ucb)->ucb_rfmSv )
#define RFM_EVENTGRAB(ucb,pri)  psema( &(ucb)->ucb_eventSema, (pri) )
#define RFM_EVENTFREE(ucb)      cvsema( &(ucb)->ucb_eventSema )
/*
 *------------------------------------------------------------------------
 * The `rfm_devflag' is used by the kernel to determine some facts about
 * the driver.  This driver is now MP safe.
 *------------------------------------------------------------------------
 */
int     rfm_devflag = (D_MP);
/*
 *------------------------------------------------------------------------
 * The `rfm_mversion' variable is required to signal that this is a
 * loadable device driver.
 *------------------------------------------------------------------------
 */
char    *rfm_mversion = M_VERSION;
/*
 *------------------------------------------------------------------------
 * Shape of per-instance unit control block (UCB)
 *------------------------------------------------------------------------
 */
typedef struct ucb_s    {
#if VMEIO
        vertex_hdl_t    ucb_conn;       /* Connection point              */
        vertex_hdl_t    ucb_vertex;     /* My vertex                     */
        vmeio_intr_t    ucb_intr;       /* VMEIO interrupt handle        */
#endif
        struct edt      *ucb_e;         /* `e' address                   */
        int             ucb_adapter;    /* VMEbus adapter number         */
        int             ucb_unit;       /* Minor device number           */
        struct buf      *ucb_bp;        /* Backlink to I/O buf           */
        uint_t          ucb_flags;      /* Activity flags                */
        vmeio_am_t      ucb_am;
#define UCB_FLAGS_OPEN  (1U << 0)       /* Device is opened              */
#define UCB_FLAGS_ETIMEO (1U << 1)      /* Device timeout occurred       */
#define UCB_FLAGS_AWAIT (1U << 2)       /* Event A wanted                */
#define UCB_FLAGS_BWAIT (1U << 3)       /* Event B wanted                */
#define UCB_FLAGS_CWAIT (1U << 4)       /* Event C wanted                */
#define UCB_FLAGS_FWAIT (1U << 5)       /* FIFO event wanted             */
#define UCB_FLAGS_AINFO (1U << 6)       /* Event A notification wanted   */
#define UCB_FLAGS_BINFO (1U << 7)       /* Event B notification wanted   */
#define UCB_FLAGS_CINFO (1U << 8)       /* Event C notification wanted   */
#define UCB_FLAGS_FINFO (1U << 9)       /* Event D notification wanted   */
#define UCB_FLAGS_DONE  (1U << 10)      /* DMA complete                  */
#define UCB_FLAGS_BERR  (1U << 11)      /* DMA had bus error             */
#define UCB_FLAGS_FOUND (1U << 12)      /* Hardware probed OK            */
        uint_t          ucb_pending;    /* What went on                  */
#define UCB_PENDING_A   (1U << 0)       /* Event A pending               */
#define UCB_PENDING_B   (1U << 1)       /* Event B pending               */
#define UCB_PENDING_C   (1U << 2)       /* Event C pending               */
#define UCB_PENDING_F   (1U << 3)       /* Event F pending               */
        dev_t           ucb_dev;        /* Device major and minor id's   */
        RFM             ucb_rfm;        /* Address of board's registers  */
        off_t           ucb_rfmSize;    /* Total size of reflective mem  */
        int             ucb_mynodeid;   /* RFM node number of my device  */
        int             ucb_bid;        /* Local copy of board ID        */
        int             ucb_sender;     /* Node ID sending last ireq     */
        int             ucb_ilev;       /* Interrupt priority level      */
        int             ucb_ivec;       /* Common interrupt vector       */
        toid_t          ucb_eventTimeoutId;/* Id of event timeout        */
        clock_t         ucb_eventWait;  /* Event timeout (usec)          */
        int             ucb_errno;      /* Status from last syscall      */
        char            ucb_name[ 32 ]; /* Name of this device           */
        char            ucb_msg[ 256 ]; /* Local message buffer          */
        rfm_perfstat_t  ucb_perfstat;   /* Statistics; don't clear       */
        void            *ucb_userProc[RFM_NEVENT];/* Process to alert    */
        int             ucb_signal[RFM_NEVENT];/* Per-event signals      */
        rfm_dmamap_t    ucb_dmaMap;     /* DMA map                       */
        int             ucb_xferCount;  /* Size of current DMA transfer  */
        caddr_t         ucb_dmaAddr;    /* Current DMA address           */
        off_t           ucb_dmaOffset;  /* Relative window DMA offset    */
        rfmdmainfo_t    ucb_dmaInfo;    /* DMA information               */
        int             ucb_lockedSize; /* Bytes locked for user DMA     */
        __userabi_t     ucb_userabi;    /* Description of user process   */
        mutex_t         ucb_mutex;      /* Upper-half exclusion lock     */
        sema_t          ucb_eventSema;  /* Event coordination            */
        sema_t          ucb_stratSema;  /* Strategy routine serializer   */
        lock_t          ucb_rfmLock;    /* Locks access to board regs    */
        sv_t            ucb_rfmSv;      /* Synchronizes access to board  */
        rfm_piomap_t    ucb_piomap;     /* How to get to board registers */
} ucb_t, *UCB;
#define UNULL   ( (UCB) NULL )          /* A UCB-typed null address      */
/*
 *------------------------------------------------------------------------
 * Per-device storage for each instance of the reflective memory driver
 *------------------------------------------------------------------------
 */
static ucb_t ucbs[ NRFM ];              /* One per device                */
/*
 *------------------------------------------------------------------------
 * debugMsg: format and display error messages (similar to cmn_err & printf)
 *------------------------------------------------------------------------
 * The first character of the format string is special: `!' messages go
 * only to the "putbuf"; `^' messages go only to the console; and `?' 
 * always goes to the "putbuf" and to the console iff we were booted in
 * verbose mode.
 *
 * N.B.: ideally, this could have been written as either a <stdarg.h> or
 * a <varargs.h> routine.  This would not compile under 5.3, so I have
 * hand-crafted a work-alike.  To restore the original intent, include the
 * <stdarg.h> file and change: 1) the declaration of `ap' to be 
 * va_list ap'; and 2) the vsprintf() call to be `vsprintf( bp, fmt, ap )'
 *------------------------------------------------------------------------
 */
static  char    *me = "rfm";            /* Generic driver name           */
static  void
debugMsg(
        register UCB    ucb,            /* Per-device info               */
        char            *fmt,           /* Printf-style format           */
        ...                             /* Args as required              */
)
{
        char            *text;          /* Which buffer we actually use  */
        register char   *bp;            /* Walks down output buffer      */
        static char     buf[ 256 ];     /* Private messaging area        */
        va_list ap;                     /* Address of arg on stack       */
        va_start( ap, fmt );
        /* Use message buffer in the UCB if we have one                  */
        if( ucb )       {
                /* Use message area specific to this device              */
                text = bp = ucb->ucb_msg;
        } else  {
                /* Don't have a UCB, so use our own private area         */
                text = bp = buf;
        }
        /* The first character of message is special to cmn_err(9F)      */
        switch( fmt[0] & 0xFF ) {
        case `!':                       /* syslog only                   */
                /*FALLTHROUGH*/
        case `^':                       /* console only                  */
                /*FALLTHROUGH*/
        case `?':                       /* syslog and verbose console    */
                *bp++ = *fmt++;
                break;
        default:
                break;
        }
        /* Prepend "rfm" and optional device numbers to message          */
        if( ucb )       {
                /* I know the numbers!                                   */
                sprintf( bp, "%s%d: ", me, ucb->ucb_unit );
        } else  {
                /* This is an anonymous message                          */
                sprintf( bp, "%s:  ", me );
        }
        while( *bp ) ++bp;              /* Advance to NULL at end        */
        vsprintf( bp, fmt, ap );        /* Output user's text            */
        cmn_err( CE_CONT, "%s.\n", text );/* Write to console            */
        /*
         * Allow message to reach the syslogd() if we must. We do this by
         * blindly waiting about 0.25 seconds after each message. This
         * should work OK, even if we happen to be in an interrupt
         * routine; some random process will be delayed, but who cares?
         * Our interrupt handlers take care to clear this flag while they
         * are running.
         *
         * On IRIX 6.4 and beyond our interrupt runs as a thread, so
         * that random process can continue running on some other
         * CPU while we sit and spin here.
         */
#if     0
        if( rfmDebug & RFM_DBSLOW )     {
                        drv_usecwait( USECONDS / 4 );
        }
#endif  /* SLOW */
}
/*
 *------------------------------------------------------------------------
 * eventTimedOut: signal that the operation has timed out
 *------------------------------------------------------------------------
 */
static  void
eventTimedOut(
        caddr_t         arg             /* Really UCB                    */
)
{
        register UCB    ucb = (UCB) arg;
        WHENDEBUG( RFM_DBTIMER )        {
                debugMsg( ucb, "event timedout" );
        }
        ucb->ucb_flags |= UCB_FLAGS_ETIMEO;
        if( !ucb->ucb_errno )   {
                ucb->ucb_errno = ETIMEDOUT;
        }
        RFM_EVENTFREE( ucb );           /* Wake up event waiter          */
}
/*
 *------------------------------------------------------------------------
 * startEventTimer: schedule an event timeout
 *------------------------------------------------------------------------
 */
static  void
startEventTimer(
        register UCB    ucb,            /* Per-board info                */
        clock_t         usec            /* Length of timeout             */
)
{
        WHENDEBUG( RFM_DBTIMER )        {
                debugMsg( ucb, "scheduling %d-usecond event timeout", usec );
        }
        ucb->ucb_flags &= ~UCB_FLAGS_ETIMEO;
        ucb->ucb_eventTimeoutId = itimeout( eventTimedOut, (void *) ucb,
                (long) drv_usectohz( usec ), plhi, 0, 0, 0 );
}
/*
 *------------------------------------------------------------------------
 * stopEventTimer: cancel a pending event timeout call
 *------------------------------------------------------------------------
 */
static  void
stopEventTimer(
        register UCB    ucb             /* Per-board info                */
)
{
        toid_t          tid;
        tid = ucb->ucb_eventTimeoutId;
        ucb->ucb_eventTimeoutId = NULL;
        untimeout( tid );
        WHENDEBUG( RFM_DBTIMER )        {
                debugMsg( ucb, "event timeout cancelled" );
        }
}
/*
 *------------------------------------------------------------------------
 * record_sender: record node id that sent this interrupt
 *------------------------------------------------------------------------
 */
static  void
record_sender(
        register UCB    ucb             /* Per-device info               */
)
{
        register RFM    rfm = ucb->ucb_rfm;
        /* Record the originating node (if possible)                     */
        switch( rfm->rfm_bid )  {
        case RFM_5550_MAGIC: ucb->ucb_sender = ~0; break;
        case RFM_5576_MAGIC: ucb->ucb_sender = rfm->rfm_sid1; break;
        case RFM_5578_MAGIC: ucb->ucb_sender = rfm->rfm_sid1; break;
        case RFM_5588_MAGIC: ucb->ucb_sender = rfm->rfm_sid1; break;
        case RFM_5588DMA_MAGIC: ucb->ucb_sender = rfm->rfm_sid1; break;
        }
        WHENDEBUG( RFM_DBINTR ) {
                debugMsg( ucb, "interrupt sent by node %d", ucb->ucb_sender );
        }
}
/*
 *------------------------------------------------------------------------
 * ireqFhandler: FIFO interrupt handler
 *------------------------------------------------------------------------
 * NB: return values from interrupt handlers are ignored.
 *------------------------------------------------------------------------
 */
static  void
ireqFhandler(
        register UCB    ucb             /* Per-device info               */
)
{
        register RFM rfm = ucb->ucb_rfm;        /* Find the board        */
        int     orfmDebug = rfmDebug;   /* Incoming debug flags          */
        rfm->rfm_cr0 |= F_ENABLE;       /* Set the flag again            */
        stopEventTimer( ucb );                  /* Cancel any timeout    */
        rfmDebug &= ~RFM_DBSLOW;        /* Prevent waiting               */
        WHENDEBUG( RFM_DBINTR ) {
                debugMsg( ucb, "transmit FIFO's half full" );
        }
        ++(ucb->ucb_perfstat.rps_nfireq);       /* Count the interrupt   */
        if( ucb->ucb_flags & UCB_FLAGS_FINFO )  {
                /* Send target process the desired signal                */
                WHENDEBUG( RFM_DBINTR ) {
                        debugMsg( ucb, "sending event F notification" );
                }
                if( ucb->ucb_userProc[RFM_EVENT_F] &&
                proc_signal( ucb->ucb_userProc[RFM_EVENT_F],
                ucb->ucb_signal[RFM_EVENT_F] ) == -1 )  {
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb,
                                        "error sending event F notification" );
                        }
                }
        } else if( ucb->ucb_flags & UCB_FLAGS_FWAIT )   {
                /* Wake up the process holding on the FIFO event         */
                RFM_EVENTFREE( ucb );
                /* Clear pending flags                                   */
                ucb->ucb_pending &= ~UCB_PENDING_F;
        } else  {
                /* Slow down access to let the FIFO drain                */
                register int    retries;
                ucb->ucb_pending |= UCB_PENDING_F;
                for( retries = 4; retries-- > 0; )      {
                        if( (rfm->rfm_csr & RFM_CSR_TXHALF) != 0 ) break;
                        drv_usecwait( USECONDS / 4 );
                }
        }
        rfmDebug = orfmDebug;           /* Restore original debug flags  */
}
/*
 *------------------------------------------------------------------------
 * interfaceSignal: release 1 process holding synchronization variable
 *------------------------------------------------------------------------
 */
static void
interfaceSignal(
        register UCB    ucb             /* Per-device info               */
)
{
        WHENDEBUG( RFM_DBMUTEX )        {
                debugMsg( ucb, "waking up process holding sv" );
        }
        (void) RFM_TELLONE( ucb );
}
/*
 *------------------------------------------------------------------------
 * dmaIreqHandler: dma complete handler
 *------------------------------------------------------------------------
 */
static  void
dmaIreqHandler(
        register UCB    ucb             /* Per-board local storage       */
)
{
        register RFM    rfm = ucb->ucb_rfm;
        int             orfmDebug = rfmDebug;/* Incoming debug flags     */
        unsigned char   int04;          /* Local copy of register        */
        ++(ucb->ucb_perfstat.rps_ndireq);       /* Count the interrupt   */
        rfmDebug &= ~RFM_DBSLOW;        /* Prevent waiting               */
        int04 = rfm->rfm_int04;         /* Get a local copy              */
        if( int04 & (RFM_INT04_BERR | RFM_INT04_LBERR) )        {
                /* VMEbus error on the transfer                          */
                ucb->ucb_flags |= UCB_FLAGS_BERR;
                rfm->rfm_int04 = (int04 & ~(RFM_INT04_BERR | RFM_INT04_LBERR));
        } else if( int04 & RFM_INT04_DONE )     {
                /* DMA complete with no errors                           */
                ucb->ucb_flags |= UCB_FLAGS_DONE;
                ucb->ucb_flags |= (int04 & ~RFM_INT04_DONE);
        }
        /* Wake up process waiting on DMA interrupt                      */
        WHENDEBUG( RFM_DBINTR ) {
                debugMsg( ucb, "waking up process" );
        }
        interfaceSignal( ucb );
        /* Clean up the DMA-related flags                                */
        rfm->rfm_int04 = (int04 & ~(RFM_INT04_BERR | RFM_INT04_LBERR |
                RFM_INT04_DONE));
        /* Set the flag again, but don't enable the interrupt            */ 
        rfm->rfm_cr4 = ((D_ENABLE & ~RFM_BIM_IRE) | ucb->ucb_ilev);
        /* Restore debug flags                                           */
        rfmDebug = orfmDebug;
}
/*
 *------------------------------------------------------------------------
 * ireqAhandler: error interrupt handler
 *------------------------------------------------------------------------
 * NB: return values from interrupt handlers are ignored.
 *------------------------------------------------------------------------
 */
static  void
ireqAhandler(
        register UCB    ucb             /* Per-board local storage       */
)
{
        register RFM    rfm = ucb->ucb_rfm;/* Find the board             */
        int             orfmDebug = rfmDebug;/* Incoming debug flags     */
        record_sender( ucb );           /* Record originating node       */
        /* Clean up the rest                                             */
        rfm->rfm_cr1 |= F_ENABLE;       /* Set the flag again            */
        stopEventTimer( ucb );          /* Cancel any timeout            */
        rfmDebug &= ~RFM_DBSLOW;        /* Prevent waiting               */
        WHENDEBUG( RFM_DBINTR ) {
                debugMsg( ucb, "event A" );
        }
        ++(ucb->ucb_perfstat.rps_naireq);       /* Count the interrupt   */
        if( ucb->ucb_flags & UCB_FLAGS_AINFO )  {
                /* Send target process the desired signal                */
                WHENDEBUG( RFM_DBINTR ) {
                        debugMsg( ucb, "sending event A notification" );
                }
                if( ucb->ucb_userProc[RFM_EVENT_A] &&
                proc_signal( ucb->ucb_userProc[RFM_EVENT_A],
                ucb->ucb_signal[RFM_EVENT_A] ) == -1 )  {
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb,
                                        "error sending event A notification" );
                        }
                }
        } else if( ucb->ucb_flags & UCB_FLAGS_AWAIT )   {
                /* Wake up the process holding on the A event            */
                RFM_EVENTFREE( ucb );
                /* Clear pending flags                                   */
                ucb->ucb_pending &= ~UCB_PENDING_A;
        } else  {
                ucb->ucb_pending |= UCB_PENDING_A;
        }
        rfmDebug = orfmDebug;           /* Restore original debug flags  */
}
/*
 *------------------------------------------------------------------------
 * ireqBhandler: transfer complete interrupt
 *------------------------------------------------------------------------
 */
static  void
ireqBhandler(
        register UCB    ucb             /* Per-board local storage       */
)
{
        register RFM    rfm = ucb->ucb_rfm;/* Find the board             */
        int             orfmDebug = rfmDebug;/* Incoming debug flags     */
        record_sender( ucb );           /* Record originating node       */
        /* Clean up the rest                                             */
        rfm->rfm_cr2 |= F_ENABLE;       /* Set the flag again            */
        stopEventTimer( ucb );          /* Cancel any timeout            */
        rfmDebug &= ~RFM_DBSLOW;        /* Prevent waiting               */
        WHENDEBUG( RFM_DBINTR ) {
                debugMsg( ucb, "event B" );
        }
        ++(ucb->ucb_perfstat.rps_nbireq);       /* Count the interrupt   */
        if( ucb->ucb_flags & UCB_FLAGS_BINFO )  {
                /* Send target process the desired signal                */
                WHENDEBUG( RFM_DBINTR ) {
                        debugMsg( ucb, "sending event B notification" );
                }
                if( ucb->ucb_userProc[RFM_EVENT_B] &&
                proc_signal( ucb->ucb_userProc[RFM_EVENT_B],
                ucb->ucb_signal[RFM_EVENT_B] ) == -1 )  {
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb,
                                        "error sending event B notification" );
                        }
                }
        } else if( ucb->ucb_flags & UCB_FLAGS_BWAIT )   {
                /* Wake up the process holding on the B event            */
                RFM_EVENTFREE( ucb );
                /* Clear pending flags                                   */
                ucb->ucb_pending &= ~UCB_PENDING_B;
        } else  {
                ucb->ucb_pending |= UCB_PENDING_B;
        }
        rfmDebug = orfmDebug;           /* Restore original debug flags  */
}
/*
 *------------------------------------------------------------------------
 * ireqChandler: restart interrupt handler
 *------------------------------------------------------------------------
 */
static  void
ireqChandler(
        register UCB    ucb             /* Per-board local storage       */
)
{
        register RFM    rfm = ucb->ucb_rfm;/* Find the board             */
        int             orfmDebug = rfmDebug;/* Incoming debug flags     */
        record_sender( ucb );           /* Record originating node       */
        /* Clean up the rest                                             */
        rfm->rfm_cr3 |= F_ENABLE;       /* Set the flag again            */
        stopEventTimer( ucb );                  /* Cancel any timeout    */
        rfmDebug &= ~RFM_DBSLOW;        /* Prevent waiting               */
        WHENDEBUG( RFM_DBINTR ) {
                debugMsg( ucb, "event C" );
        }
        ++(ucb->ucb_perfstat.rps_ncireq);       /* Count the interrupt   */
        if( ucb->ucb_flags & UCB_FLAGS_CINFO )  {
                /* Send target process the desired signal                */
                WHENDEBUG( RFM_DBINTR ) {
                        debugMsg( ucb, "sending event C notification" );
                }
                if( ucb->ucb_userProc[RFM_EVENT_C] &&
                proc_signal( ucb->ucb_userProc[RFM_EVENT_C],
                ucb->ucb_signal[RFM_EVENT_C] ) == -1 )  {
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb,
                                        "error sending event C notification" );
                        }
                }
        } else if( ucb->ucb_flags & UCB_FLAGS_CWAIT )   {
                /* Wake up the process holding on the C event            */
                RFM_EVENTFREE( ucb );
                /* Clear pending flags                                   */
                ucb->ucb_pending &= ~UCB_PENDING_C;
        } else  {
                ucb->ucb_pending |= UCB_PENDING_C;
        }
        rfmDebug = orfmDebug;           /* Restore original debug flags  */
}
/*
 *------------------------------------------------------------------------
 * interfaceLock: lock a basic lock, return the cookie
 *------------------------------------------------------------------------
 */
static int
interfaceLock(
        register UCB    ucb,            /* Per-device info               */
        pl_t            pl              /* Priority level                */
)
{
        WHENDEBUG( RFM_DBMUTEX )        {
                debugMsg( ucb, "locking interface" );
        }
        return( RFM_LOCK( ucb, pl ) );
}
/*
 *------------------------------------------------------------------------
 * interfaceUnlock: release lock on the interface
 *-------------------- x----------------------------------------------------
 */
static void
interfaceUnlock(
        register UCB    ucb,            /* Per-device info               */
        int             cookie          /* Cookie from "interfaceLock()" */
)
{
        WHENDEBUG( RFM_DBMUTEX )        {
                debugMsg( ucb, "unlocking interface" );
        }
        RFM_UNLOCK( ucb, cookie );
}
/*
 *------------------------------------------------------------------------
 * interfaceWait: wait on synchronization variable
 *------------------------------------------------------------------------
 */
static int
interfaceWait(
        register UCB    ucb,            /* Per-device info               */
        pl_t            pl,             /* Level to grab lock            */
        int             cookie          /* Cookie from "interfaceLock()" */
)
{
        WHENDEBUG( RFM_DBMUTEX )        {
                debugMsg( ucb, "about to wait on synch variable" );
        }
        RFM_WAIT( ucb, cookie );
        WHENDEBUG( RFM_DBMUTEX )        {
                debugMsg( ucb, "got the sync variable" );
        }
        return( RFM_LOCK( ucb, pl ) );
}
/*
 *------------------------------------------------------------------------
 * rfm_intr: common code to all interrupts
 *------------------------------------------------------------------------
 */
int
rfm_intr(
#if VMEIO
        intr_arg_t      arg             /* arbitrary pattern */
#else
        int             unit            /* Unit number                   */
#endif
)
{
#if VMEIO
        UCB             ucb = (UCB)arg;
#else
        register UCB    ucb = &ucbs[unit];
#endif 
        register RFM    rfm = ucb->ucb_rfm;
        int             x;              /* Incoming SPL level            */
        x = interfaceLock( ucb, plhi );
        if(      (rfm->rfm_cr4 & RFM_BIM_F) == 0 ) dmaIreqHandler( ucb );
        else if( (rfm->rfm_cr0 & RFM_BIM_F) == 0 ) ireqFhandler( ucb );
        else if( (rfm->rfm_cr1 & RFM_BIM_F) == 0 ) ireqAhandler( ucb );
        else if( (rfm->rfm_cr2 & RFM_BIM_F) == 0 ) ireqBhandler( ucb );
        else if( (rfm->rfm_cr3 & RFM_BIM_F) == 0 ) ireqChandler( ucb );
        else WHENDEBUG( RFM_DBINTR )    {
                debugMsg( ucb, "suprious interrupt" );
        }
        interfaceUnlock( ucb, x );
        return( 0 );            /* Value is ignored, but lint complains  */
}
/*
 *------------------------------------------------------------------------
 * probeDevice: verify that the RFM device is present
 *------------------------------------------------------------------------
 * We verify that the device exists by reading the board ID register
 * located at the same relative offset on every type of reflective memory
 * device.
 *------------------------------------------------------------------------
 */
static  int
probeDevice(
        register UCB    ucb             /* Per-board storage             */
)
{
        register RFM    rfm = ucb->ucb_rfm;/* Address of this device     */
        unsigned char   bid = rfm->rfm_bid;/* Suspected board ID         */
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( ucb, "board ID register = 0x%X", bid );
        }
        switch( (ucb->ucb_bid = bid) )  {
        default:                        /* Unknown board ID              */
                WHENDEBUG( RFM_DBERROR )        {
                        debugMsg( ucb,
                                "board at 0x%X on VME %d is not an RFM board (bid=0x%X)",
                                ucb->ucb_e->e_space[0].ios_iopaddr,
                                ucb->ucb_e->e_adap,
                                bid );
                }
                return( -1 );
        case RFM_5588DMA_MAGIC:         /* Fiber-optic interconnect      */
                ucb->ucb_mynodeid = rfm->rfm_nid;/* Get my node ID       */
                rfm->rfm_csr = 0;       /* Turn off the LED              */
                break;
        case RFM_5588_MAGIC:            /* Fiber-optic interconnect      */
                ucb->ucb_mynodeid = rfm->rfm_nid;/* Get my node ID       */
                rfm->rfm_csr = 0;       /* Turn off the LED              */
                break;
        case RFM_5578_MAGIC:            /* Fiber-optic interconnect      */
                ucb->ucb_mynodeid = rfm->rfm_nid;/* Get my node ID       */
                rfm->rfm_csr = 0;       /* Turn off the LED              */
                break;
        case RFM_5576_MAGIC:            /* Fiber-optic interconnect      */
                ucb->ucb_mynodeid = rfm->rfm_nid;/* Get my node ID       */
                rfm->rfm_csr = 0;       /* Turn off the LED              */
                break;
        case RFM_5550_MAGIC:            /* Metallic interconnect         */
                ucb->ucb_mynodeid = rfm->rfm_csr & 0xF;/* Get my node ID */
                rfm->rfm_csr = 0;       /* Turn off the LED              */
                break;
        }
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( ucb, "probe of 0x%X succeeded", rfm );
        }
        return( 0 );
}
/*
 *------------------------------------------------------------------------
 * setupHardware: clear the board, dump interrupts
 *------------------------------------------------------------------------
 */
static  int
setupHardware(
        register UCB    ucb             /* Per-board info                */
)
{
        register RFM    rfm = ucb->ucb_rfm;/* Make easy to find          */
        unsigned char   bid;            /* Observed board ID             */
        /* Turn on the failure LED while we poke around                  */
        rfm->rfm_csr = RFM_CSR_LED;     /* Works whatever board type     */
        /* Disable all interrupts                                        */
        rfm->rfm_cr0 = 0;
        rfm->rfm_cr1 = 0;
        rfm->rfm_cr2 = 0;
        rfm->rfm_cr3 = 0;
        rfm->rfm_cr4 = 0;
        ucb->ucb_pending = 0;           /* Nothing is now pending        */
        /* Reset the interface                                           */
        switch( (bid = rfm->rfm_bid) )  {
        default:
                {
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb, "unknown board id (0x%X)",
                                        bid );
                        }
                        return( (ucb->ucb_errno = EINVAL) );
                }
                /*NOTREACHED*/
        case RFM_5550_MAGIC:            /* Metallic 5550                 */
                {
                        strcpy( ucb->ucb_name, "VMIVME-5550" );
                }
                break;
        case RFM_5576_MAGIC:            /* Fiber-optic 5576              */
                {
                        strcpy( ucb->ucb_name, "VMIVME-5576" );
                        /*
                         * Drop anything that may be in the board's
                         * receive FIFO's. Do this by first clearing the
                         * CSR's flags and then doing a benign write to
                         * the board (we'll use the board ID register, but
                         * any location in the first RFM_REGSIZ bytes
                         * would work, but writing the board ID register
                         * won't really change anything on tbe board). Any
                         * write to the board (even to the registers) is
                         * propagated around the fiber optic ring. We poke
                         * the board and then check that we saw our write
                         * come back around.
                         */
                        rfm->rfm_csr = 0;       /* Clear all flags       */
                        rfm->rfm_bid = RFM_5576_MAGIC;
                        rfm->rfm_bid = RFM_5576_MAGIC;
                        rfm->rfm_bid = RFM_5576_MAGIC;
                        drv_usecwait( 1 + (USECONDS / 100) );
                        if( rfm->rfm_csr & RFM_OCSR_OWNDAT )    {
                                /* Flush the interrupt FIFO's            */
                                rfm->rfm_sid1 = 0;
                                rfm->rfm_sid2 = 0;
                                rfm->rfm_sid3 = 0;
                        } else WHENDEBUG( RFM_DBERROR ) {
                                debugMsg( ucb, "fiber-optic ring not intact" );
                        }
                }
                break;
        case RFM_5578_MAGIC:            /* Fiber-optic 5578              */
                {
                        strcpy( ucb->ucb_name, "VMIVME-5578" );
                        /* Clear the violation bit if needed             */
                        if( rfm->rfm_irs & RFM_IRS_VIOLAT )     {
                                /* Strobe RFM_IRS_RPL to resync PLL      */
                                rfm->rfm_irs = (RFM_IRS_RPL|RFM_IRS_LVIOLAT);
                                rfm->rfm_irs;
                                rfm->rfm_irs = (RFM_IRS_LVIOLAT);
                                rfm->rfm_irs;
                        }
                        /*
                         * Drop anything that may be in the board's
                         * receive FIFO's. Do this by first clearing the
                         * CSR's flags and then doing a benign write to
                         * the board (we'll use the node ID register, but
                         * any location in the first RFM_REGSIZ bytes
                         * would work, but writing the node ID register
                         * won't really change anything on tbe board). Any
                         * write to the board (even to the registers) is
                         * propagated around the fiber optic ring. We poke
                         * the board and then check that we saw our write
                         * come back around.
                         */
                        rfm->rfm_csr = 0;       /* Clear all flags       */
                        rfm->rfm_nid = 0;
                        rfm->rfm_nid = 0;
                        rfm->rfm_nid = 0;
                        drv_usecwait( 1 + (USECONDS / 100) );
                        if( rfm->rfm_csr & RFM_OCSR_OWNDAT )    {
                                /* Flush the interrupt FIFO's            */
                                rfm->rfm_sid1 = 0;
                                rfm->rfm_sid2 = 0;
                                rfm->rfm_sid3 = 0;
                        } else WHENDEBUG( RFM_DBERROR ) {
                                debugMsg( ucb, "fiber-optic ring not intact" );
                        }
                }
                break;
        case RFM_5588DMA_MAGIC:         /* Fiber-optic 5588 w/DMA        */
                /*FALLTHROUGH*/
        case RFM_5588_MAGIC:            /* Fiber-optic 5588              */
                {
                        strcpy( ucb->ucb_name,
                                ((bid == RFM_5588DMA_MAGIC) ? 
                                        "VMIVME-5588DMA" : "VMIVME-5588") );
                        /* Clear the violation bit if needed             */
                        if( rfm->rfm_irs & RFM_IRS_VIOLAT )     {
                                /* Strobe RFM_IRS_RPL to resync PLL      */
                                rfm->rfm_irs = (RFM_IRS_RPL|RFM_IRS_LVIOLAT);
                                rfm->rfm_irs;
                                rfm->rfm_irs = (RFM_IRS_LVIOLAT);
                                rfm->rfm_irs;
                        }
                        /*
                         * Drop anything that may be in the board's
                         * receive FIFO's. Do this by first clearing the
                         * CSR's flags and then doing a benign write to
                         * the board (we'll use the node ID register, but
                         * any location in the first RFM_REGSIZ bytes
                         * would work, but writing the node ID register
                         * won't really change anything on tbe board). Any
                         * write to the board (even to the registers) is
                         * propagated around the fiber optic ring. We poke
                         * the board and then check that we saw our write
                         * come back around.
                         */
                        rfm->rfm_csr = 0;       /* Clear all flags       */
                        rfm->rfm_nid = 0;
                        rfm->rfm_nid = 0;
                        rfm->rfm_nid = 0;
                        drv_usecwait( 1 + (USECONDS / 100) );
                        if( rfm->rfm_csr & RFM_OCSR_OWNDAT )    {
                                /* Flush the interrupt FIFO's            */
                                rfm->rfm_sid1 = 0;
                                rfm->rfm_sid2 = 0;
                                rfm->rfm_sid3 = 0;
                        } else WHENDEBUG( RFM_DBERROR ) {
                                debugMsg( ucb, "fiber-optic ring not intact" );
                        }
                }
                break;
        }
        /* Get the interrupt vector and set as common vector             */
        rfm->rfm_vr0 = ucb->ucb_ivec;
        rfm->rfm_vr1 = ucb->ucb_ivec;
        rfm->rfm_vr2 = ucb->ucb_ivec;
        rfm->rfm_vr3 = ucb->ucb_ivec;
        rfm->rfm_vr4 = ucb->ucb_ivec;
        /* Get the interrupt csr values and set them on board            */
        rfm->rfm_cr0 = F_ENABLE | ucb->ucb_ilev;
        rfm->rfm_cr1 = A_ENABLE | ucb->ucb_ilev;
        rfm->rfm_cr2 = B_ENABLE | ucb->ucb_ilev;
        rfm->rfm_cr3 = C_ENABLE | ucb->ucb_ilev;
        rfm->rfm_cr4 = C_ENABLE | ucb->ucb_ilev;
        rfm->rfm_csr = 0;       /* Turn off the failure LED              */
        return( 0 );
}
/*
 *------------------------------------------------------------------------
 * loadDmaInfo: setup DMA control registers
 *------------------------------------------------------------------------
 */
static  void
loadDmaInfo(
        register UCB            ucb     /* Per-board info                */
)
{
        register RFM            rfm = ucb->ucb_rfm;
        int                     bid;    /* Observed board ID             */
        switch( (bid = rfm->rfm_bid) )  {
        default:
                {
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb,
                        "loadDmaInfo() didn't recognize board ID of 0x%X",
                                        bid );
                        }
                }
                break;
        case RFM_5588DMA_MAGIC:
                {
                        unsigned char   dmac0;/* Local copy              */
                        unsigned char   dmac1;/* Local copy              */
                        /* Construct DMAC0                               */
                        dmac0 = ucb->ucb_dmaInfo.rdi_burst & RFM_DMAC0_BMASK;
                        /* Construct DMAC1                               */
                        dmac1 = 0;
                        switch( ucb->ucb_dmaInfo.rdi_relmode )  {
                        default:
                                {
                                        WHENDEBUG( RFM_DBERROR )        {
                                                debugMsg( ucb,
                                                "unknown rdi_relmode of %d",
                                                ucb->ucb_dmaInfo.rdi_relmode );
                                        }
                                }
                                break;
                        case RDI_RELMODE_ROR:
                                dmac1 |= RFM_DMAC1_RELMD_ROR;
                                break;
                        case RDI_RELMODE_RWD:
                                dmac1 |= RFM_DMAC1_RELMD_RWD;
                                break;
                        case RDI_RELMODE_ROC:
                                dmac1 |= RFM_DMAC1_RELMD_ROC;
                                break;
                        case RDI_RELMODE_BCAP:
                                dmac1 |= RFM_DMAC1_RELMD_BCAP;
                                break;
                        }
                        dmac1 |= (ucb->ucb_dmaInfo.rdi_busreq <<
                                RFM_DMAC1_LEVEL_SHIFT) & RFM_DMAC1_LEVEL_MASK;
                        dmac1 |= (ucb->ucb_dmaInfo.rdi_intrLeave <<
                                RFM_DMAC1_ILEAV_SHIFT) & RFM_DMAC1_ILEAV_MASK;
                        /* Update the hardware                           */
                        rfm->rfm_dmac0 = dmac0;
                        rfm->rfm_dmac1 = dmac1;
                }
                break;
        case RFM_5588_MAGIC:
                break;
        case RFM_5578_MAGIC:
                break;
        case RFM_5576_MAGIC:
                break;
        case RFM_5550_MAGIC:
                break;
        }
}
/*
 *------------------------------------------------------------------------
 * rfm_init: preliminary initialization
 *------------------------------------------------------------------------
 * This routine cannot be called concurrently, so no mutex is needed.
 *------------------------------------------------------------------------
 */
void
rfm_init(
        void
)
{
        /* everything that was in here,
         * has moved to rfm_edtinit().
         */
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( UNULL, "driver initializing" );
        }
}
/*
 *------------------------------------------------------------------------
 * rfm_edtinit: early device table (init board & interrupts at boot time)
 *------------------------------------------------------------------------
#if VMEIO
 * This routine may be called concurrently for devices on different
 * VME busses; a mutex may be required if global data is modified.
#else
 * This routine cannot be called concurrently, so no mutex is needed.
#endif
 *------------------------------------------------------------------------
 */
int
rfm_edtinit(edt_t *e)
{
        vme_intrs_t     *intrs = e->e_bus_info;
        vmeio_am_t      am;
        iopaddr_t       vmeaddr = e->e_space[0].ios_iopaddr;
        size_t          size = e->e_space[0].ios_size;
#if VMEIO 
        int             unit = e->e_ctlr;
        vertex_hdl_t    conn = e->e_connectpt;
        vmeio_intr_t    intr;
        int             rv;
        graph_error_t   rc;
        vertex_hdl_t    rfm;
        char            mutexName[512];
        char            semaName[512];
        char            svName[512];
        char            stratName[512];
#else
        int             unit = intrs->v_unit;
#endif
        int             ivec = intrs ? intrs->v_vec : 0;
        int             ilev = intrs ? intrs->v_brl : 0;
        register UCB    ucb;
#if VMEIO
        if (conn == 0) {
                /* no connection point in edt ...
                 * probably called from edt_init().
                 */
                return -1;
        }
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg(NULL, "%v: rfm_attach()", conn);
        }
        NEW(ucb);
        ASSERT(ucb != 0);
        ucb->ucb_conn = conn;
        am = iospace_to_vmeioam(e->e_space[0].ios_type);
#else
        /* Fill in enough of the UCB to allow us to print                */
        ucb = &ucbs[unit];
        ucb->ucb_dev  = makedevice( 0, unit );
#endif 
        ucb->ucb_unit = unit;
        ucb->ucb_flags = 0;
        /*
         * Using information from the VECTOR: line, we establish a
         * fixed I/O map to allow us to access the device's registers.
         * Notice that we don't specifically define the size of the register
         * space; this gets picked up from the `rfm.sm' master file entry.
         * Therefore, the `rfm.sm' IOSPACE entry must include the size of
         * the *entire* reflective memory, not just the RFM_REGSIZ bytes
         * in the front of it.
         */
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( ucb,
                "busType=%d, adapter=%d, unit=%d, am=0x%x, iopbaddr=0x%lX, size=0x%X",
                        e->e_bus_type, e->e_adap, unit,
                        am, vmeaddr, size);
        }
#if VMEIO
        ASSERT(am == VMEIO_AM_A32 | VMEIO_AM_S); 
        ucb->ucb_am = am;
        ucb->ucb_piomap = vmeio_piomap_alloc
                (conn, 0, am, vmeaddr, size, size, 0);
#else
        ucb->ucb_piomap = pio_mapalloc( e->e_bus_type, e->e_adap,
                &e->e_space[0], PIOMAP_FIXED, "rfm" );
#endif 
        if ( ucb->ucb_piomap == 0 )       {
                /* 
                 * This could fail because the adapter isn't valid
                 * or invalid addresses or there are no more fixed
                 * mappings available in the case of A32.
                 */
                debugMsg(ucb, "cannot map device registers" );
                return(0);
        }
        ucb->ucb_e = e;
#if VMEIO
        e->e_base = vmeio_piomap_addr
                (ucb->ucb_piomap, vmeaddr, size);
#else
        e->e_base = pio_mapaddr( ucb->ucb_piomap, vmeaddr );
#endif
        if (e->e_base == 0) {
                debugMsg(ucb, "can alloc PIO map");
        }
        ucb->ucb_rfm = (RFM) e->e_base;
        ucb->ucb_rfmSize = (off_t) size;
        WHENDEBUG(RFM_DBINIT) {
                debugMsg(ucb, "VMEaddr=0x%lX, kernel=0x%lX",
                         (unsigned long) vmeaddr, 
                         (unsigned long) ucb->ucb_rfm);
        }
        /* 
         * Now that we think that we know where the board is, check
         * to see if it's one of ours.
         */
        if( badaddr(&(ucb->ucb_rfm->rfm_bid), 1) || probeDevice( ucb ) ) {
                WHENDEBUG( RFM_DBERROR )        {
                        debugMsg( ucb, "board not found at 0x%X on VME bus %d",
                                ucb->ucb_e->e_space[0].ios_iopaddr,
                                ucb->ucb_e->e_adap);
                }
                goto BailOut;
        }
#if VMEIO
        /* Now that we know the device is here, add it
         * to the hardware graph.
         */
        rc = hwgraph_char_device_add(conn, EDGE_LBL_RFM, "rfm_", &rfm);
        if (rc != GRAPH_SUCCESS) {
                ASSERT(0);
                return(-1);
        }
        ucb->ucb_vertex = rfm; 
        device_info_set(rfm, ucb);
        /* [try to] create the convenience link.
         */
        {
            vertex_hdl_t        cvhdl;
            char                name[32];
            cvhdl = GRAPH_VERTEX_NONE;
            hwgraph_path_add(hwgraph_root, EDGE_LBL_RFM, &cvhdl);
            sprintf(name, "%d", unit);
            if (cvhdl != GRAPH_VERTEX_NONE)
                hwgraph_edge_add(cvhdl, rfm, name);
        }
#endif
        ucb->ucb_adapter = e->e_adap;
#if VMEIO
        /* VMEIO can use levels and vectors that we
         * assign, or it can assign them if we tell
         * it to do so.
         */
        intr = vmeio_intr_alloc
                (conn, 0, ivec, ilev, rfm, 0);
        if (intr == 0) {
                WHENDEBUG( RFM_DBERROR )        {
                        debugMsg( ucb, "cannot allocate interrupt resource" );
                }
                goto BailOut;
        }
        ucb->ucb_intr = intr;
        /* Find out the vector vmeio has assigned us;
         * complain if the result is not appropriate.
         */
        if (ivec == VMEIO_INTR_VECTOR_ANY)
                ivec = vmeio_intr_vector_get(intr);
        else if (ivec != vmeio_intr_vector_get(intr))
                cmn_err(CE_WARN, "rfm%d intr alloc error:\n"
                        "\twanted interrupt vector %d\n"
                        "\tgot interrupt vector %d\n",
                        unit, ivec, vmeio_intr_vector_get(intr));
#if 0
        /* vmeio_intr_level_get doesn't exist. (yet?) */
        if (ilev == VMEIO_INTR_LEVEL_NONE)
                ilev = vmeio_intr_level_get(intr);
        else if (ilev != vmeio_intr_level_get(intr))
                cmn_err(CE_WARN, "rfm%d intr alloc error:\n"
                        "\twanted interrupt level %d\n"
                        "\tgot interrupt level %d\n",
                        unit, ilev, vmeio_intr_level_get(intr));
#endif
#else
        ivec = vme_ivec_alloc( ucb->ucb_adapter );
        if ( ucb->ucb_ivec == -1 )       {
                WHENDEBUG( RFM_DBERROR )        {
                        debugMsg( ucb, "cannot allocate interrupt vector" );
                }
                goto BailOut;
        }
#endif 
        ucb->ucb_ilev = ilev;
        ucb->ucb_ivec = ivec;
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg(ucb, "vector=0x%X, level=%d", ivec, ilev);
        }
#if VMEIO
        rv = vmeio_intr_connect
            (intr, (intr_func_t) rfm_intr, (intr_arg_t)ucb, 0);
        if (rv == -1) {
                debugMsg(ucb, "cannot connection interrupt handler");
                goto BailOut;
        }
#else
        vme_ivec_set( ucb->ucb_adapter, ucb->ucb_ivec,
                      (int (*)(int)) rfm_intr, unit );
#endif 
        /* Initialize the software state and the board                   */
        ucb->ucb_flags = UCB_FLAGS_FOUND;
        /* Setup for the DMA */
        ucb->ucb_dmaMap = DMAMAP_FAILED;
        
        if( setupHardware(ucb) )        {
                WHENDEBUG( RFM_DBERROR )        {
                        debugMsg( ucb, "unknown board type; not installed" );
                }
                goto BailOut;
        }
        /* Load the random board registers                               */
        ucb->ucb_rfm->rfm_dmac2 = RFM_DMAC2_DWID_D64 
#if VMEIO
            | VMEbus_AMR_A32SMBLT
#else
            | addrSpaces[unit].vs_vmeAmr
#endif
            ;
        /* Ok                                                            */
        WHENDEBUG( RFM_DBOPEN ) {
                debugMsg( ucb, "device is a %ld-Kbyte %s",
                        ucb->ucb_rfmSize / 1024L,
                        ucb->ucb_name );
        }
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( ucb, "creating mutex's and sema's" );
        }
        sprintf( mutexName, "rfm%d", ucb->ucb_unit);
        MUTEX_INIT( &ucb->ucb_mutex, MUTEX_DEFAULT, mutexName );
        /* more per-UCB date initialization */
        /* The "EVENT" semaphore is initially not available      */
        sprintf( semaName, "rfm%d", ucb->ucb_unit);
        initnsema( &ucb->ucb_eventSema, 0, semaName );
        /* The "STRAT" semaphone is initially available          */
        sprintf( stratName, "rfm%d", ucb->ucb_unit);
        initnsema( &ucb->ucb_stratSema, 1, stratName );
        /* The "SV" is used for DMA locking                      */
        sprintf( svName, "rfm%d", ucb->ucb_unit);
        SV_INIT( &ucb->ucb_rfmSv, SV_DEFAULT, svName );
        RFM_LOCK_INIT(ucb);
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg(ucb, "rfm_edtinit() finished");
        }
        return (RFM_REGSIZ);
        /*
         *================================================================
         * Some error is preventing us from installing ourselves so we
         * need to clean up as well as we can.
         *================================================================
         */
BailOut:
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( ucb, "edtinit failed" );
        }
        if( ucb->ucb_flags & UCB_FLAGS_FOUND )  {
                WHENDEBUG( RFM_DBINIT ) {
                        debugMsg( ucb, "freeing adapter %d vector %d",
                                ucb->ucb_adapter, ucb->ucb_ivec );
                }
#if VMEIO
                if (ucb->ucb_intr)
                        vmeio_intr_free(ucb->ucb_intr);
#else
                vme_ivec_free (ucb->ucb_adapter, ucb->ucb_ivec );
#endif 
        }
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( ucb, "unmapping device registers" );
        }
#if VMEIO
        if (ucb->ucb_piomap)
                vmeio_piomap_free(ucb->ucb_piomap); /* Unmap the device registers */
#else
        pio_mapfree( ucb->ucb_piomap ); 
#endif 
        ucb->ucb_rfm = 0;               /* Forget where device is        */
        ucb->ucb_piomap = 0;            /* Forget the map, also          */
#if VMEIO
        DEL(ucb);
#endif
        return( 0 );
}
/*
 *------------------------------------------------------------------------
 * rfm_start: called after rfminit() and rfmedtinit()
#if VMEIO
 * In the VMEIO world, rfm_start() is rather useless
 * since it gets called before we find any dynamically
 * located VME busses (like the XIO-VME adapter).
#endif
 *------------------------------------------------------------------------
 * This routine cannot be called concurrently, although interrupts are
 * alive at this point.
 *------------------------------------------------------------------------
 */
void
rfm_start(void)
{
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( UNULL, "started" );
        }
}
/*
 *------------------------------------------------------------------------
 * topHalfLock: set mutual exclusion of top-half of system call handlers
 *------------------------------------------------------------------------
 */
static void
topHalfLock(register UCB ucb)   /* Per-device info               */
{
        WHENDEBUG( RFM_DBMUTEX )        {
                debugMsg( ucb, "locking top half" );
        }
        TOPHALF_LOCK( ucb );
}
/*
 *------------------------------------------------------------------------
 * topHalfUnlock: unlock top half of system call handlers
 *------------------------------------------------------------------------
 */
static void
topHalfUnlock(
        register UCB    ucb             /* Per-device info               */
)
{
        WHENDEBUG( RFM_DBMUTEX )        {
                debugMsg( ucb, "unlocking top half" );
        }
        TOPHALF_UNLOCK( ucb );
}
/*
 *------------------------------------------------------------------------
 * rfm_open: called in response to the open(2) system call
 *------------------------------------------------------------------------
 * Only the `dev' argument is really used.  The others are all ignored,
 * except for `otyp' -- we look at that to catch layered opens.  Multiple
 * user-level open's are OK.
 *------------------------------------------------------------------------
 */
int
rfm_open(
        dev_t           *devp,          /* Complex device number addr    */
        int             oflag,          /* Open(2) flags (not used)      */
        int             otyp,           /* Open(2) type                  */
        cred_t          *crp            /* Credentials (not used)        */
)
{
        dev_t           dev = *devp;
#if VMEIO
        vertex_hdl_t    vhdl = dev_to_vhdl(dev);
        UCB             ucb = device_info_get(vhdl);
        int             unit = ucb->ucb_unit;   /* for printing */
        vertex_hdl_t    conn = ucb->ucb_conn;   /* for vmeio */
#else
        int             unit = RFMUNIT(dev);    /* Extract unit number   */
        UCB             ucb = &ucbs[unit];
#endif
#if !VMEIO
        if( unit > NRFM )       {
                WHENDEBUG( RFM_DBOPEN ) {
                        debugMsg( NULL, "no such unit (%d)", unit );
                }
                return( ENODEV );
        }
#endif 
        topHalfLock(ucb);
        if( ucb->ucb_rfm == (RFM) NULL )        {
                WHENDEBUG( RFM_DBERROR )        {
                        debugMsg(ucb, "attempt to open missing unit %d", unit);
                }
                topHalfUnlock(ucb);
                return( ENODEV );
        }
        if( otyp == OTYP_BLK )  {
                WHENDEBUG( RFM_DBOPEN ) {
                        debugMsg( ucb, "illegal open type (%d)", otyp );
                }
                topHalfUnlock(ucb);
                return( EINVAL );
        }
        if( (ucb->ucb_flags & UCB_FLAGS_OPEN) == 0 )    {
                /* Not opened before                                     */
                int     eventId;        /* Loops across notifications    */
                /* Make sure that there are no notifications left over   */
                ucb->ucb_pending = 0;
                for( eventId = 0; eventId < RFM_NEVENT; ++eventId )     {
                        ucb->ucb_userProc[eventId] = 0;
                }
                /* Set the default event wait time                       */
                ucb->ucb_eventWait = EVENTPATIENCE * USECONDS;
                /* Set the default DMA wait time                         */
                ucb->ucb_dmaInfo = defaultDmaInfo;
                ucb->ucb_dev = dev;
                if( userabi(&ucb->ucb_userabi)) {
                        /* Somehow we got called without a user context! */
                        WHENDEBUG( RFM_DBOPEN ) {
                                debugMsg( ucb, "no userabi context" );
                        }
                        topHalfUnlock(ucb);
                        return( ESRCH );
                }
                WHENDEBUG( RFM_DBOPEN ) {
                        debugMsg( ucb,
                                "userabi(int=%d,long=%d,ptr=%d,longlong=%d)",
                                ucb->ucb_userabi.uabi_szint,
                                ucb->ucb_userabi.uabi_szlong,
                                ucb->ucb_userabi.uabi_szptr,
                                ucb->ucb_userabi.uabi_szlonglong );
                }
                loadDmaInfo( ucb );
                if( setupHardware( ucb ) != 0 ) {
                        int             errno = ucb->ucb_errno;
                        topHalfUnlock(ucb);
                        return( errno );
                }
                /* Create the DMA mapping window if board supports DMA   */
                if( ucb->ucb_rfm->rfm_bid == RFM_5588DMA_MAGIC )        {
                        WHENDEBUG( RFM_DBOPEN ) {
                                debugMsg( ucb, "allocating DMA map" );
                        }
#if VMEIO
                        ucb->ucb_dmaMap = vmeio_dmamap_alloc
                                (conn, 0, ucb->ucb_am, ucb->ucb_rfmSize, 0);
#else
                        ucb->ucb_dmaMap = dma_mapalloc(
                                addrSpaces[ unit ].vs_type,
                                ucb->ucb_adapter,
                                (int) btopr( ucb->ucb_rfmSize ), 0 );
#endif
                        if (ucb->ucb_dmaMap == DMAMAP_FAILED) {
                                WHENDEBUG( RFM_DBERROR ) {
                                        debugMsg( ucb,
                                                  "cannot create %d-page dma map",
                                                  btopr( ucb->ucb_rfmSize ) );
                                }
                                topHalfUnlock(ucb);
                                return( ENOMEM );
                        }
                } else  {
                        /* Does not support DMA, so don't need a DMA map */
                        ucb->ucb_dmaMap = DMAMAP_FAILED;
                }
                ucb->ucb_flags |= UCB_FLAGS_OPEN;
        }
        topHalfUnlock(ucb);
        return( 0 );
}
/*
 *------------------------------------------------------------------------
 * disableRfmInterrupts: disable all interrupts on the reflective memory
 *------------------------------------------------------------------------
 */
static void
disableRfmInterrupts(
        register UCB    ucb             /* Per-board info address        */
)
{
        register RFM    rfm = ucb->ucb_rfm;
        /*
         * It is not too difficult to disable interrupts, just zero
         * the interrupt enables in each BIM control register.  Just
         * to be thorough, we will also clean up the UCB, but this
         * really isn't necessary since interrupts are being disabled
         * only because the device is being closed.
         */
        rfm->rfm_cr0 = 0;
        rfm->rfm_cr1 = 0;
        rfm->rfm_cr2 = 0;
        rfm->rfm_cr3 = 0;
        rfm->rfm_cr4 = 0;
        ucb->ucb_pending = 0;
}
/*
 *------------------------------------------------------------------------
 * notificationControl: turn notification on or off
 *------------------------------------------------------------------------
 */
static  int
notificationControl(
        register UCB    ucb,            /* Per-device local storage      */
        RFM_EVENT       evp             /* Event control pointer         */
)
{
        int             flag;           /* Event flag bit                */
        char            *eventName;     /* Name of the event             */
        int             *sigp;          /* Addr of signal action         */
        int             eventId;        /* Event code (subscript)        */
        switch( (eventId = evp->event) )        {
        default:
                {
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "unknown event code %d",
                                        eventId );
                        }
                        ucb->ucb_errno = EINVAL;
                }
                return( -1 );
        case RFM_EVENT_A:
                {
                        eventName = "A";
                        flag = UCB_FLAGS_AINFO;
                }
                break;
        case RFM_EVENT_B:
                {
                        eventName = "B";
                        flag = UCB_FLAGS_BINFO;
                }
                break;
        case RFM_EVENT_C:
                {
                        eventName = "C";
                        flag = UCB_FLAGS_CINFO;
                }
                break;
        case RFM_EVENT_F:
                {
                        eventName = "F";
                        flag = UCB_FLAGS_FINFO;
                }
                break;
        case RFM_EVENT_DMA:
                {
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb,
                                        "no DMA interrupt notification" );
                        }
                        ucb->ucb_errno = EINVAL;
                }
                return( -1 );
        }
        /* Disconnect from user process (even if this isn't our event)   */
        if( ucb->ucb_userProc[eventId] )        {
                proc_unref( ucb->ucb_userProc[eventId] );
                ucb->ucb_userProc[eventId] = 0;
        }
        /* Manipulate the notification event                             */
        sigp = &ucb->ucb_signal[evp->event];
        if( evp->sig == 0 )     {
                WHENDEBUG( RFM_DBIOCTL )        {
                        debugMsg( ucb, "event %s notification disabled",
                                eventName );
                }
                ucb->ucb_flags &= ~flag;
                *sigp = 0;
        } else  {
                WHENDEBUG( RFM_DBIOCTL )        {
                        debugMsg( ucb,
                                "send signal %d for event %s notification",
                                evp->sig, eventName );
                }
                ucb->ucb_userProc[eventId] = proc_ref();
                *sigp = evp->sig;
                ucb->ucb_flags |= flag;
        }
        return( 0 );
}
/*
 *------------------------------------------------------------------------
 * removeNotifications: cancel all notifications for a process
 *------------------------------------------------------------------------
 */
static void
removeNotifications(
        register UCB    ucb             /* Per-device info               */
)
{
        int             eventId;        /* Event loop controller         */
        for( eventId = 0; eventId < RFM_NEVENT; ++eventId )     {
                rfm_event_t     eventInfo;
                /* Build packet to turn off notification                 */
                if( eventId != RFM_EVENT_DMA )  {
                        eventInfo.event = eventId;
                        eventInfo.sig = 0;
                        (void) notificationControl( ucb, &eventInfo );
                }
        }
}
/*
 *------------------------------------------------------------------------
 * rfm_close: called after last process having device open close(2)'s it.
 *------------------------------------------------------------------------
 */
int
rfm_close(
        dev_t           dev,            /* Complex device number         */
        int             openflags,      /* Open(2) flags (not used)      */
        int             otyp,           /* Open(2) type (not used)       */
        cred_t          *credp          /* Credentials (ignored)         */
)
{
#if VMEIO
        vertex_hdl_t    rfm = dev_to_vhdl(dev);
        UCB             ucb = device_info_get(rfm);
        int             unit = ucb->ucb_unit;   /* for printing */
        vertex_hdl_t    conn = ucb->ucb_conn;   /* for vmeio */
#else
        int             unit = RFMUNIT(dev);    /* Extract unit number   */
        UCB             ucb = &ucbs[unit];
#endif
        topHalfLock(ucb);
        WHENDEBUG( RFM_DBCLOSE )        {
                debugMsg(ucb, "rfm%d closing device", unit);
        }
        /* Clean up the UCB (keep only UCB_FLAGS_FOUND flag)             */
        ucb->ucb_flags &= UCB_FLAGS_FOUND;
        ucb->ucb_pending = 0;
        removeNotifications( ucb );
        /* Clean up the hardware                                         */
        disableRfmInterrupts( ucb );
        /* If a DMA map was allocated, we can free it now                */
        if (ucb->ucb_dmaMap != DMAMAP_FAILED) {
                WHENDEBUG( RFM_DBCLOSE )        {
                        debugMsg( ucb, "freeing DMA map" );
                }
#if !VMEIO
                dma_mapfree( ucb->ucb_dmaMap );
#else
                vmeio_dmamap_free(ucb->ucb_dmaMap);
#endif /* VMEIO */
        }
        WHENDEBUG( RFM_DBCLOSE )        {
                debugMsg( ucb, "so long until tomorrow" );
        }
        topHalfUnlock(ucb);
        return (0);
}
/*
 *------------------------------------------------------------------------
 * uioDump: decode UIO structure and write to syslog
 *------------------------------------------------------------------------
 */
static  void
uioDump(
        register UCB    ucb,            /* Per-board info                */
        struct uio      *uio,           /* User-I/O structure            */
        char            *why            /* Kind of I/O being done        */
)
{
        int             i;              /* Generic loop counter          */
        char            *space;         /* Address space name            */
        switch( uio->uio_segflg )       {
        default:                space = "UNKNOWN";              break;
        case UIO_SYSSPACE:      space = "UIO_SYSSPACE";         break;
        case UIO_USERSPACE:     space = "UIO_USERSPACE";        break;
        }
        debugMsg( ucb, "%s UIO dump", why );
        debugMsg( ucb, "uio_segflg = %s", space );
        debugMsg( ucb, "uio_fmode = 0%o", uio->uio_fmode );
        debugMsg( ucb, "uio_offset = 0x%lX", (long) uio->uio_offset );
        debugMsg( ucb, "uio_resid = 0x%lX", (long) uio->uio_resid );
        debugMsg( ucb, "uio_iovcnt = 0x%X", uio->uio_iovcnt );
        for( i = 0; i < uio->uio_iovcnt; ++i )  {
                iovec_t         *iov = &uio->uio_iov[i];
                debugMsg( ucb, "uio_iov[%d] = (base 0x%lX, len 0x%lX)",
                        i, (long) iov->iov_base, (long) iov->iov_len );
        }
}
/*
 *------------------------------------------------------------------------
 * copyRfmData: copy buffer using optimal transfers
 *------------------------------------------------------------------------
 */
static  int
copyRfmData(
        UCB                     ucb,    /* Per-board info                */
        register caddr_t        src,    /* Source buffer KVA             */
        register caddr_t        dst,    /* Destination buffer KVA        */
        register long           len     /* Number of bytes               */
)
{
        register RFM            rfm = ucb->ucb_rfm;
        register int            retries;
        /* Make sure transmit FIFO's are not too full                    */
DrainFIFO:
        retries = 0;
        while( (rfm->rfm_csr & RFM_CSR_TXHALF) == 0 )   {
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb, "txhalf" );
                }
                if( retries-- <= 0 )    {
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb, "rfm clogged" );
                        }
                        return( EIO );
                }
                drv_usecwait( 25 );
        }
        /* Try to advance to next `32-bit' boundary                      */
        while( (len > 0) && ((((long)src) % sizeof(int32_t)) != 0) )    {
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb, 
                        "a-copy (src=0x%X, dst=0x%X, len=0x%X)",
                                src, dst, len );
                }
                *dst++ = *src++;
                --len;
        }
        /* Try 32-bit transfers if possible                              */
        if( (rfm->rfm_csr & RFM_CSR_TXHALF) == 0 )      {
                goto DrainFIFO;
        }
        if( (len >= sizeof(int32_t)) &&
        ((((long)src)|((long)dst)) % sizeof(int32_t)) == 0 )    {
                register int32_t        *isrc;
                register int32_t        *idst;
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb, "32-copy (src=0x%X, dst=0x%X, len=0x%X)",
                                src, dst, len );
                }
                /* Aligned to an `int' boundary                          */
                isrc = (int32_t *) src;
                idst = (int32_t *) dst;
                while( len >= sizeof(int32_t) ) {
                        *idst++ = *isrc++;
                        len -= sizeof(int32_t);
                }
                src = (caddr_t) isrc;
                dst = (caddr_t) idst;
        }
        /* Try 16-bit transfers if possible                              */
        if( (rfm->rfm_csr & RFM_CSR_TXHALF) == 0 )      {
                goto DrainFIFO;
        }
        if( (len >= sizeof(int16_t)) &&
        ((((long)src)|((long)dst)) % sizeof(int16_t)) == 0 )    {
                register int16_t        *isrc;
                register int16_t        *idst;
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb,
                                "16-copy (src=0x%X, dst=0x%X, len=0x%X)",
                                src, dst, len );
                }
                /* Aligned to a `int16_t' boundary                       */
                isrc = (int16_t *) src;
                idst = (int16_t *) dst;
                while( len >= sizeof(int16_t) ) {
                        *idst++ = *isrc++;
                        len -= sizeof(int16_t);
                }
                src = (caddr_t) isrc;
                dst = (caddr_t) idst;
        }
        /* Copy any remaining bytes                                      */
        if( len > 0 )   {
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb, "8-copy (src=0x%X, dst=0x%X, len=0x%X)",
                                src, dst, len );
                }
                if( (rfm->rfm_csr & RFM_CSR_TXHALF) == 0 )      {
                        goto DrainFIFO;
                }
                while( len-- > 0 )      {
                        *dst++ = *src++;
                }
        }
        return( 0 );
}
/*
 *------------------------------------------------------------------------
 * generalStrategy: perform physical I/O based on buffer descriptor
 *------------------------------------------------------------------------
 */
static  int
generalStrategy(
        register struct buf     *bp     /* Buffer descriptor address     */
)
{
        dev_t           dev = bp->b_edev;
#if VMEIO
        vertex_hdl_t    vhdl = dev_to_vhdl(dev);
        UCB             ucb = device_info_get(vhdl);
        int             unit = ucb->ucb_unit;   /* for printing */
#else
        int             unit = RFMUNIT(dev);    /* Extract unit number   */
        UCB             ucb = &ucbs[unit];
#endif
        register RFM    rfm = ucb->ucb_rfm;
        int             err;            /* I/O results                   */
        int             x;              /* Previous CPU interrupt level  */
        int             hasDma;         /* True if RFM has DMA engine    */
        int             Pass;
        caddr_t         userBuffer;     /* Walks down the user area      */
        /* Since there is only one DMA resource, serialize this routine  */
        psema( &ucb->ucb_stratSema, PZERO+1 );
        /* Decode the buffer structure if we are asked                   */
        WHENDEBUG( RFM_DBSTRAT )        {
                char            msg[ 512 ];
                char            *mp = msg;
                int             flags;
                BFLAGS          bfp;
                BFLAGS          lbfp;
                int             leadin;
                flags = bp->b_flags;
                leadin = `\0';
                for( bfp = bflags, lbfp = bflags + Nbflags;
                bfp < lbfp; ++bfp )     {
                        if( flags & bfp->bf_value )     {
                                char    *tp = bfp->bf_name;
                                if( leadin )    {
                                        *mp++ = leadin;
                                }
                                while( (*mp++ = *tp++) ) continue;
                                --mp;
                                flags &= ~(bfp->bf_value);
                                leadin = `,';
                        }
                }
                if( flags )     {
                        sprintf( mp, "[leftover=0x%X]", flags );
                        while( *mp ) ++mp;
                }
                *mp = `\0';
                debugMsg( ucb, "bp->b_flags = 0x%X (%s)", bp->b_flags, msg );
        }
        /* Decide if we can DMA at all                                   */
        hasDma = (rfm->rfm_bid == RFM_5588DMA_MAGIC);
        WHENDEBUG( RFM_DBSTRAT )        {
                debugMsg( ucb, "rfm board %s do DMA",
                        (hasDma ? "can" : "cannot") );
        }
        /* Whole buffer remains to be transferred                        */
        bp->b_resid = bp->b_bcount;
        /* NB. The SGI "how to write a driver" documents say that the 
        driver can change the "bp->b_dmaaddr" field. Well, you can't if
        you're using the "uiophysio" interface like we are! */
        userBuffer = bp->b_dmaaddr;
        /* Disable interrupts                                            */
        x = interfaceLock( ucb, plhi );
        if( hasDma )    {
                if( bp->b_flags & B_READ )      {
                        /* Map region as DMA readable                    */
                        rfm->rfm_dmac0 &= ~RFM_DMAC0_H2RFM;
                } else  {
                        /* Map region as DMA writable                    */
                        rfm->rfm_dmac0 |= RFM_DMAC0_H2RFM;
                }
        }
        /* Do until all tranfers are done or until an error              */
        Pass = 1;
        for( err = 0; (err == 0) && (bp->b_resid > 0); )        {
                long    quadAlignment;  /* Byte alignment within 64-bits */
                long    nbytes;         /* Byte count of data to move    */
                int     useDma;         /* True if use DMA engine        */
                int     useIrq;         /* True if use DMA interrupt     */
                off_t   rfmLeft;        /* Bytes to end of RFM space     */
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb,
                                "Pass=%d, bp_resid=%lu, ucb_dmaOffset=0x%lX",
                                Pass++, (long) bp->b_resid,
                                (long) ucb->ucb_dmaOffset );
                }
                /* Limit the transfer to amount of RFM that is left      */
                rfmLeft = ucb->ucb_rfmSize - ucb->ucb_dmaOffset;
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb,
                        "ucb_rfmSize=0x%lX, ucb_dmaOffset=0x%lX, rfmLeft=0x%lX",
                                (long) ucb->ucb_rfmSize,
                                (long) ucb->ucb_dmaOffset,
                                (long) rfmLeft );
                }
                if( rfmLeft <= 0 ) break;
                /* Decide if signs and portents are conducive to DMA     */
                quadAlignment = ((long) userBuffer) & (long) (DMAWIDTH-1);
                WHENDEBUG( RFM_DBSTRAT )        {
                        debugMsg( ucb, "quadAlignment=0x%lX", quadAlignment );
                }
                if( hasDma == 0 )       {
                        /*
                         * No DMA, don't bother trying.
                         */
                        useDma = 0;
                        nbytes = min( bp->b_resid, rfmLeft );
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb, "%ld-byte hobson's choice PIO",
                                        nbytes );
                        }
                } else if( quadAlignment !=
                (ucb->ucb_dmaOffset & (long) (DMAWIDTH-1)) ) {
                        /*
                         * Not aligned to same offset within a quadword.
                         */
                        useDma = 0;
                        nbytes = min( bp->b_resid, rfmLeft );
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb, "%ld-byte unaligned PIO",
                                        nbytes );
                        }
                } else if( quadAlignment )      {
                        /*
                         * Although both ends of the DMA are aligned to
                         * the same byte of a quadword, we are not yet
                         * aligned to the beginning of a quadword. Compute
                         * the number of bytes to get us to the next
                         * quadword boundary (via PIO) and then try again
                         * to DMA.
                         */
                        useDma = 0;
                        nbytes = min( bp->b_resid, (DMAWIDTH-quadAlignment) );
                        nbytes = min( nbytes, rfmLeft );
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb, "making %ld-byte quad alignment",
                                        nbytes );
                        }
                } else  {
                        /*
                         * Well, well, both ends of the DMA transfer are
                         * now aligned to a quadword boundary. Depending
                         * on the actual number of bytes to be
                         * transferred, we may actually get to do the DMA.
                         * Since this driver does DMA in D64 mode, we can
                         * only transfer multiples of 64-bits.
                         */
                        nbytes = min( bp->b_resid, rfmLeft ) & RFM_DMAL_MASK;
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb, "considering dma of 0x%lX bytes",
                                        nbytes );
                        }
                        if( nbytes < DMAWIDTH ) {
                                /* Too short for DMA, use PIO            */
                                WHENDEBUG( RFM_DBSTRAT )        {
                                        debugMsg( ucb,
                                                "shorter than a quad (0x%lX)",
                                                nbytes );
                                }
                                useDma = 0;
                                nbytes = min( bp->b_resid, rfmLeft );
                        } else if( nbytes <
                        ucb->ucb_dmaInfo.rdi_minDma )   {
                                /* Would not be worth the trouble        */
                                WHENDEBUG( RFM_DBSTRAT )        {
                                        debugMsg( ucb,
                        "avoiding runt dma (%lu bytes), using PIO instead",
                                                nbytes );
                                }
                                useDma = 0;
                                nbytes = min( bp->b_resid, (long) rfmLeft );
                        } else if( nbytes <
                        ucb->ucb_dmaInfo.rdi_minDmaIreq )       {
                                /* DMA is OK, but don't use interrupt    */
                                WHENDEBUG( RFM_DBSTRAT )        {
                                        debugMsg( ucb,
                                "%lu-byte dma doesn't qualify for interrupt",
                                                nbytes );
                                }
                                useDma = hasDma;
                                useIrq = 0;
                        } else  {
                                /* Must be quite a long, aligned block   */
                                WHENDEBUG( RFM_DBSTRAT )        {
                                        debugMsg( ucb,
                                        "interrupts OK for %lu-byte dma",
                                                nbytes );
                                }
                                useDma = hasDma;
                                useIrq = 1;
                        }
                }
                /* Perform either a DMA transfer or a PIO copy           */
                if( useDma )    {
ViaDMA:
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb, "beginning DMA operations" );
                        }
#if VMEIO
#if 0
                        /* here's the simple way to do it:
                         */
                        rfm->rfm_vdma = (unsigned int) 
                                vmeio_dmamap_addr(ucb->ucb_dmaMap,
                                                  kvtophys(userBuffer),
                                                  nbytes);
#else
                        {
                                /* Here's the more complex way to do it,
                                 * using alenlists and such:
                                 */
                                alenlist_t al;
                                alenlist_t vme_al;
                                iopaddr_t vmeaddr;
                                size_t    byte_count;
                                
                                al = kvaddr_to_alenlist(0, 
                                                        userBuffer,
                                                        nbytes,
                                                        0);
                                ASSERT(al != 0);
                                vme_al = vmeio_dmamap_list(ucb->ucb_dmaMap,
                                                           al, 
                                                           VMEIO_INPLACE);
                                /* Note that if the userBuffer crosses a
                                 * page boundary, the initial list will
                                 * be broken up into one block per
                                 * physical page involved; and INPLACE
                                 * will prevent combining of consecutive
                                 * blocks. In other words, alenlist_size
                                 * will not be "1" and byte_count will
                                 * be less than, not equal to, nbytes.
                                 */
                
                                ASSERT(vme_al == al);
                                ASSERT(alenlist_size(vme_al) == 1);
                                alenlist_cursor_init(vme_al, 0, 0);
                                alenlist_get(vme_al, 0, 0,
                                             (alenaddr_t *) &vmeaddr,
                                             &byte_count,
                                             0);
                                ASSERT(byte_count == nbytes);
                                alenlist_done(vme_al);
                                rfm->rfm_vdma = (unsigned) vmeaddr;
                                
                        }
#endif
                        
#else
                        nbytes = dma_map( ucb->ucb_dmaMap, userBuffer,
                                (int) nbytes );
#endif 
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb, "dma map length = 0x%lX",
                                        nbytes );
                        }
#if VMEIO 
                        if (rfm->rfm_vdma == 0) {
#else
                        if( nbytes <= 0 )       {
#endif
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb, "dma_map failed" );
                                }
#if VMEIO
                                vmeio_dmamap_done(ucb->ucb_dmaMap);
#endif 
                                err = EIO;
                                break;
                        } 
                        /* Clear DMA status flags                */
                        rfm->rfm_int04 &= ~( RFM_INT04_BERR | RFM_INT04_LBERR |
                                RFM_INT04_DONE);
                        /* RFM has DMA engine and I/O is long enough     */
#if VMEIO
                        rfm->rfm_dmac2 = RFM_DMAC2_DWID_D64 | 
                                VMEbus_AMR_A32SMBLT;
#else
                        rfm->rfm_dmac2 = RFM_DMAC2_DWID_D64 |
                                addrSpaces[ucb->ucb_unit].vs_vmeAmr;
#endif 
                        /* Setup each end of the DMA pipe                */
                        rfm->rfm_ldma = (uint32_t) ucb->ucb_dmaOffset;
#if !VMEIO                    
                        rfm->rfm_vdma = (uint32_t) dma_mapaddr( ucb->ucb_dmaMap,
                                userBuffer );
#endif 
                        rfm->rfm_dmal = (uint32_t) nbytes;
                        if( useIrq )    {
                                WHENDEBUG( RFM_DBSTRAT )        {
                                        debugMsg( ucb, "interrupt dma go" );
                                }
                                /* Pend for DMA complete interrupt       */
                                ucb->ucb_flags &= ~( UCB_FLAGS_DONE |
                                        UCB_FLAGS_BERR );
                                rfm->rfm_cr4 = (D_ENABLE | ucb->ucb_ilev);
                                rfm->rfm_dmac3 = RFM_DMAC3_GO;
                                /*
                                 *========================================
                                 * There is no DMA timeout because there
                                 * is no way to abort a DMA operation; it
                                 * just darn well better happen. For this
                                 * reason, we don't bother to catch
                                 * signals either; there's nothing we
                                 * could to with it anyway.
                                 *========================================
                                 */
                                x = interfaceWait( ucb, plhi, x );
                                if( ucb->ucb_flags & UCB_FLAGS_BERR ) {
                                        /* Bus error on one end or other */
                                        WHENDEBUG( RFM_DBERROR )        {
                                                debugMsg(ucb, "dma bus error");
                                        }
                                        err = EFAULT;
                                        nbytes = 0;
                                } else WHENDEBUG( RFM_DBSTRAT ) {
                                        debugMsg( ucb, "interrupt dma done" );
                                }
                        } else  {
                                unsigned char   int04;
                                WHENDEBUG( RFM_DBSTRAT )        {
                                        debugMsg( ucb, "polled dma go" );
                                }
                                rfm->rfm_cr4 = ((D_ENABLE & ~RFM_BIM_IRE) |
                                        ucb->ucb_ilev);
                                rfm->rfm_dmac3 = RFM_DMAC3_GO;
                                for( ; ; )      {
                                        int04 = rfm->rfm_int04;
                                        if( int04 & RFM_INT04_DONE )    {
                                                break;
                                        }
                                        /*
                                         * Don't stare at the board, the
                                         * CPU will hog the bus and fight
                                         * the DMA engine for cycles.
                                         */
                                        if( ucb->ucb_dmaInfo.rdi_polling > 0 ) {
                                                drv_usecwait(
                                                ucb->ucb_dmaInfo.rdi_polling );
                                        }
                                }
                                if( int04 &
                                (RFM_INT04_BERR | RFM_INT04_LBERR) ) {
                                        err = EFAULT;
                                        WHENDEBUG( RFM_DBERROR )        {
                                                debugMsg( ucb,
                                                        "polled dma bus error");
                                        }
                                } else  WHENDEBUG( RFM_DBSTRAT )        {
                                        debugMsg( ucb, "polled dma complete" );
                                }
                                /* Clear the DMA status flags            */
                                rfm->rfm_int04 &= ~(RFM_INT04_BERR |
                                        RFM_INT04_LBERR | RFM_INT04_DONE);
                        }
                } else  {
                        /* No DMA or not long enough, use PIO            */
ViaPIO:
                        WHENDEBUG( RFM_DBSTRAT )        {
                                debugMsg( ucb,
                                        "%lu-byte PIO transfer to offset 0x%lX",
                                        nbytes,
                                        (long) ucb->ucb_dmaOffset );
                        }
                        /* Move the data manually                        */
                        if( bp->b_flags & B_READ )      {
                                /* User wants some data          */
                                err = copyRfmData( ucb,
                                ((caddr_t) rfm) + ucb->ucb_dmaOffset,
                                        userBuffer, nbytes );
                        } else  {
                                /* Use has data we want          */
                                err = copyRfmData( ucb, 
                                        userBuffer,
                                ((caddr_t) rfm) + ucb->ucb_dmaOffset, 
                                        nbytes );
                        }
                }
                if( err ) break;
                userBuffer += nbytes;
                bp->b_resid -= nbytes;
                ucb->ucb_dmaOffset += nbytes;
        }
Fini:
        interfaceUnlock( ucb, x );      /* Allow interrupt-level access  */
        if( err )       {
                bioerror( bp, err );
        }
        biodone( bp );                  /* Release uiophysio()           */
        vsema( &ucb->ucb_stratSema );   /* Release this routine          */
        return( err );
}
/*
 *------------------------------------------------------------------------
 * rfm_read: called by kernel to service a read(2) system call
 *------------------------------------------------------------------------
 */
int
rfm_read(
        dev_t           dev,            /* Complex device numbers        */
        struct uio      *uio,           /* User-I/O structure            */
        cred_t          *credp          /* Credentials (IGNORED)         */
)
{
#if VMEIO
        vertex_hdl_t    vhdl = dev_to_vhdl(dev);
        UCB             ucb = device_info_get(vhdl);
        int             unit = ucb->ucb_unit;   /* for printing */
#else
        int             unit = RFMUNIT(dev);    /* Extract unit number   */
        UCB             ucb = &ucbs[unit];
#endif
        int             results;
        topHalfLock(ucb);
        WHENDEBUG( RFM_DBREAD ) {
                uioDump( ucb, uio, "rfmread" );
        }
        ucb->ucb_dmaOffset = uio->uio_offset;
        results = uiophysio( generalStrategy, (struct buf *) NULL, dev,
                B_READ, uio );
        topHalfUnlock(ucb);
        return( results );
}
/*
 *------------------------------------------------------------------------
 * rfm_write: called by kernel to service a write(2) system call
 *------------------------------------------------------------------------
 */
int
rfm_write(
        dev_t           dev,            /* Complex device number         */
        struct uio      *uio,           /* User-I/O structure            */
        cred_t          *credp          /* Credentials (IGNORED)         */
)
{
#if VMEIO
        vertex_hdl_t    vhdl = dev_to_vhdl(dev);
        UCB             ucb = device_info_get(vhdl);
        int             unit = ucb->ucb_unit;   /* for printing */
#else
        int             unit = RFMUNIT(dev);    /* Extract unit number   */
        UCB             ucb = &ucbs[unit];
#endif
        int             results;
        topHalfLock(ucb);
        WHENDEBUG( RFM_DBWRITE )        {
                uioDump( ucb, uio, "rfmwrite" );
        }
        ucb->ucb_dmaOffset = uio->uio_offset;
        results = uiophysio( generalStrategy, (struct buf *) NULL, dev,
                B_WRITE, uio );
        topHalfUnlock(ucb);
        return( results );
}
/*
 *------------------------------------------------------------------------
 * awaitSpecificEvent: pend waiting for specific event to happen
 *------------------------------------------------------------------------
 */
static  void
awaitSpecificEvent(
        register UCB    ucb,            /* Per-board local storage       */
        register uint_t pending,        /* Event indication pending      */
        register uint_t wants,          /* Event indication wanted       */
        char            *spelling       /* Name of event (for debug)     */
)
{
        if( ucb->ucb_pending & pending )        {
                /* At least one event of this type is outstanding        */
                ucb->ucb_pending &= ~pending;
                WHENDEBUG( RFM_DBIOCTL )        {
                        debugMsg( ucb, "using stored event %s indication", 
                                spelling );
                }
        } else  {
                /*
                 * No event of that type just yet, so flag that we want
                 * it, schedule a timeout (if a period is defined), and
                 * then wait for the event to happen
                 */
                ucb->ucb_flags |= wants;
                startEventTimer( ucb, ucb->ucb_eventWait );
                if( psema( (sema_t *) &ucb->ucb_eventSema, RFMSLEEP ) ) {
                        /* Got a signal before we saw the event          */
                        stopEventTimer( ucb );
                        WHENDEBUG( RFM_DBTIMER )        {
                                debugMsg( ucb,
                                        "event %s wait interrupted by signal",
                                        spelling );
                        }
                        ucb->ucb_errno = EINTR;
                        ucb->ucb_flags |= UCB_FLAGS_ETIMEO;
                }
                /*
                 * If we have a timeout flag, then we didn't get the event
                 * because of a timeout or a signal.
                 */
                if( (ucb->ucb_flags & UCB_FLAGS_ETIMEO) &&
                (ucb->ucb_errno == 0) ) {
                        ucb->ucb_errno = ETIMEDOUT;
                }
        }
        ucb->ucb_flags &= ~wants;       /* Don't want it anymore         */
}
/*
 *------------------------------------------------------------------------
 * dumpdmaInfo: print a decoded version of DMA info for debug purposes
 *------------------------------------------------------------------------
 */
static void
dumpDmaInfo(
        register UCB    ucb,            /* Per-board info                */
        volatile rfmdmainfo_t *rdi      /* RFM dma information           */
)
{
        char            *spelling;      /* Generic name pointer          */
        debugMsg( ucb, "dma polling interval is %d usec", rdi->rdi_polling );
        debugMsg( ucb, "dma burst length is %d cycles", rdi->rdi_polling );
        switch( rdi->rdi_relmode )      {
        default:                spelling = "UNKNOWN";   break;
        case RDI_RELMODE_ROR:   spelling = "ROR";       break;
        case RDI_RELMODE_RWD:   spelling = "RWD";       break;
        case RDI_RELMODE_ROC:   spelling = "ROC";       break;
        case RDI_RELMODE_BCAP:  spelling = "BCAP";      break;
        }
        debugMsg( ucb, "%s bus release mode (%d)", spelling, rdi->rdi_relmode );
        debugMsg( ucb, "burst interleave is %d (%d nsec)", rdi->rdi_intrLeave,
                rdi->rdi_intrLeave * 250);
        debugMsg( ucb, "bus request level is %d", rdi->rdi_busreq );
        debugMsg( ucb, "minimum DMA transfer is %d bytes", rdi->rdi_minDma );
        debugMsg( ucb, "minimum DMA transfer w/interrupt is %d bytes",
                rdi->rdi_minDmaIreq );
}
/*
 *------------------------------------------------------------------------
 * rfm_ioctl: called by kernel to service an ioctl(2) system call
 *------------------------------------------------------------------------
 */
int
rfm_ioctl(
        dev_t           dev,            /* Complex device number         */
        int             cmd,            /* Command code                  */
        void            *arg,           /* Argument to command           */
        int             mode,           /* Open(2) flags (IGNORED)       */
        cred_t          *crp,           /* Credentials (IGNORED)         */
        int             *rvalp          /* Return value pointer (UNUSED) */
)
{
#if VMEIO
        vertex_hdl_t    vhdl = dev_to_vhdl(dev);
        UCB             ucb = device_info_get(vhdl);
        int             unit = ucb->ucb_unit;   /* for printing */
#else
        int             unit = RFMUNIT(dev);    /* Extract unit number   */
        UCB             ucb = &ucbs[unit];
#endif
        register RFM    rfm = ucb->ucb_rfm;
        char            *spelling = "UNKNOWN";/* Name of ioctl command   */
        int             retval;         /* Results of system call        */
        topHalfLock(ucb);
        WHENDEBUG( RFM_DBIOCTL )        {
                debugMsg( ucb, "ioctl arg = 0x%X", (caddr_t) arg );
        }
        ucb->ucb_errno = 0;
        /* Dispatch based on the command code                            */
        switch( cmd ) {
        default:
                {
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb,
                                        "unknown ioctl(2) command = 0x%X",
                                        cmd );
                        }
                        if( !ucb->ucb_errno ) ucb->ucb_errno = ENOTTY;
                }
                break;
        case RFM_RESET:                 /* Reset the interrupt stuff     */
                {
                        spelling = "RFM_RESET";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s: board reset", spelling );
                        }
                        setupHardware( ucb );
                }
                break;
        case RFM_ENABL_DIAG_MSG:        /* Turn on all debug messages    */
                {
                        spelling = "RFM_ENABL_DIAG_MSG";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s: enable messages",
                                        spelling );
                        }
                        rfmDebug = ~0;
                }
                break;
        case RFM_DISABL_DIAG_MSG:       /* Turn off all debug messages   */
                {
                        spelling = "RFM_DISABL_DIAG_MSG";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s: disable messages",
                                        spelling );
                        }
                        rfmDebug = 0;
                }
                break;
        case RFM_DEBUG:                 /* Set new debug flags           */
                {
                        uint_t  debug;
                        spelling = "RFM_DEBUG";
                        if( copyin( (caddr_t) arg, (caddr_t) &debug,
                        sizeof(debug) ) )
                                goto BadCopy;
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s: new debug level = 0x%X",
                                        spelling, debug );
                        }
                        rfmDebug = debug;
                }
                break;
        case RFM_GDEBUG:                /* Return current debug flags    */
                {
                        spelling = "RFM_GDEBUG";
                        if( copyout( (caddr_t) &rfmDebug, (caddr_t) arg,
                                sizeof(rfmDebug)) ) goto BadCopy;
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s debug level = 0x%X",
                                        spelling, rfmDebug );
                        }
                }
                break;
        case RFM_PERFSTAT:              /* Retrieve performance stats    */
                {
                        spelling = "RFM_PERFSTAT";
                        if( copyout( (caddr_t) &ucb->ucb_perfstat,
                        (caddr_t) arg, sizeof(rfm_perfstat_t))) goto BadCopy;
                }
                break;
        case RFM_ZEROSTAT:              /* Clear the performance stats   */
                {
                        spelling = "RFM_ZEROSTAT";
                        bzero( (caddr_t) &ucb->ucb_perfstat,
                                sizeof( rfm_perfstat_t ) );
                }
                break;
        case RFM_DUMP_REGS:             /* Copy device regs to console   */
                {
                        spelling = "RFM_DUMP_REGS";
                        WHENDEBUG( RFM_DBIOCTL ) debugMsg(ucb, "%s", spelling);
                        ucb->ucb_errno = EINVAL;
                }
                break;
        case RFM_EVTA_WAIT:             /* Wait on event A               */
                {
                        spelling = "RFM_EVTA_WAIT";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        awaitSpecificEvent( ucb, UCB_PENDING_A,
                                UCB_FLAGS_AWAIT, "A" );
                }
                break;
        case RFM_EVTB_WAIT:             /* Wait on event B               */
                {
                        spelling = "RFM_EVTB_WAIT";
                        WHENDEBUG( RFM_DBIOCTL ) debugMsg( ucb, "%s",
                                spelling );
                        awaitSpecificEvent( ucb, UCB_PENDING_B,
                                UCB_FLAGS_BWAIT, "B" );
                }
                break;
        case RFM_EVTC_WAIT:             /* Wait on event C               */
                {
                        spelling = "RFM_EVTC_WAIT";
                        WHENDEBUG( RFM_DBIOCTL ) debugMsg( ucb, "%s",
                                spelling );
                        awaitSpecificEvent( ucb, UCB_PENDING_C,
                                UCB_FLAGS_CWAIT, "C" );
                }
                break;
        case RFM_FIFO_WAIT:             /* Wait on event F               */
                {
                        spelling = "RFM_FIFO_WAIT";
                        WHENDEBUG( RFM_DBIOCTL ) debugMsg( ucb, "%s",
                                spelling );
                        awaitSpecificEvent( ucb, UCB_PENDING_F,
                                UCB_FLAGS_FWAIT, "F" );
                }
                break;
#if     0                               /* THIS IS A DUPLICATED ALIAS    */
        case RFM_RTN_MEM_SIZE:          /* Get size of reflective memory */
#endif                                  /* THIS IS A DUPLICATED ALIAS    */
        case RFM_GMEMSIZE:              /* Get size of reflective memory */
                {
                        int             memory = (int) ucb->ucb_rfmSize;
                        spelling = "RFM_GMEMSIZE";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyout( (caddr_t) &memory, (caddr_t) arg,
                        sizeof(memory))) goto BadCopy;
                }
                break;
        case RFM_PEEK:
                {
                        rfm_atom_t ra;  /* Atomic operation descriptor   */
                        spelling = "RFM_PEEK";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyin( (caddr_t) arg, (caddr_t) &ra,
                        sizeof(ra) ) ) goto BadCopy;
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "peek offset = 0x%X, size=%d",
                                        ra.ra_offset, ra.ra_size );
                        }
                        /* Validate parameters                           */
                        if( ra.ra_offset > ucb->ucb_rfmSize )   {
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb,
                        "attempted peek offset (0x%lX) > rfm size (0x%lX)",
                                                (long) ra.ra_offset,
                                                (long) ucb->ucb_rfmSize );
                                }
                                ucb->ucb_errno = EINVAL;
                                goto Fini;
                        }
                        /* Get the RFM contents                          */
                        switch( ra.ra_size )    {
                        default:
                                {
                                        WHENDEBUG( RFM_DBERROR )        {
                                                debugMsg( ucb,
                                        "bad peek size (%d) at offset 0x%X",
                                                        ra.ra_size,
                                                        ra.ra_offset );
                                        }
                                        ucb->ucb_errno = EINVAL;
                                }
                                goto Fini;
                        case 1:
                                {
                                        ra.ra_contents =
                                                rfm->U.b[ra.ra_offset];
                                }
                                break;
                        case 2:
                                {
                                        ra.ra_contents =
                                                rfm->U.w[ra.ra_offset/2];
                                }
                                break;
                        case 4:
                                {
                                        ra.ra_contents =
                                                rfm->U.l[ra.ra_offset/4];
                                }
                                break;
                        }
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb,
                                        "%d bytes at offset 0x%X are 0x%X",
                                        ra.ra_size, ra.ra_offset,
                                        ra.ra_contents );
                        }
                        if( copyout( (caddr_t) &ra, (caddr_t) arg,
                        sizeof(ra))) goto BadCopy;
                }
                break;
        case RFM_POKE:
                {
                        rfm_atom_t ra;  /* Atomic operation descriptor   */
                        spelling = "RFM_POKE";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyin( (caddr_t) arg, (caddr_t) &ra,
                        sizeof(ra) ) ) goto BadCopy;
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb,
                                        "poke offset=0x%X, size=%d, data=0x%X",
                                        ra.ra_offset, ra.ra_size,
                                        ra.ra_contents );
                        }
                        /* Validate parameters                           */
                        if( ra.ra_offset > ucb->ucb_rfmSize )   {
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb,
                        "attempted poke offset (0x%lX) > rfm size (0x%lX)",
                                                (long) ra.ra_offset,
                                                (long) ucb->ucb_rfmSize );
                                }
                                ucb->ucb_errno = EINVAL;
                                goto Fini;
                        }
                        /* Set the RFM contents                          */
                        switch( ra.ra_size )    {
                        default:
                                {
                                        WHENDEBUG( RFM_DBERROR )        {
                                                debugMsg( ucb,
                                        "bad poke size (%d) at offset 0x%X",
                                                        ra.ra_size,
                                                        ra.ra_offset );
                                        }
                                        ucb->ucb_errno = EINVAL;
                                }
                                goto Fini;
                        case 1:
                                {
                                        rfm->U.b[ra.ra_offset] =
                                                ra.ra_contents;
                                }
                                break;
                        case 2:
                                {
                                        rfm->U.w[ra.ra_offset/2] =
                                                ra.ra_contents;
                                }
                                break;
                        case 4:
                                {
                                        rfm->U.l[ra.ra_offset/4] =
                                                ra.ra_contents;
                                }
                                break;
                        }
                }
                break;
        case RFM_SET_TIMEOUT:           /* Set timeout period (seconds)  */
                {
                        uint_t  secs;   /* Timeout value in seconds      */
                        clock_t usec;   /* Timeout value in microseconds */
                        spelling = "RFM_SET_TIMEOUT";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyin( (caddr_t) arg, (caddr_t) &secs,
                        sizeof(secs) ) ) goto BadCopy;
                        usec = secs * USECONDS;
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb,
                                "event timeout is now %d seconds (%lu usec)",
                                        secs, usec );
                        }
                        ucb->ucb_eventWait = usec;
                }
                break;
        case RFM_GET_TIMEOUT:           /* Get timeout period (seconds)  */
                {
                        uint_t  secs;
                        uint_t  usec;
                        spelling = "RFM_GET_TIMEOUT";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        secs =  ucb->ucb_eventWait / USECONDS;
                        usec = ucb->ucb_eventWait % USECONDS;
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb,
                                        "event timeout is now %d.06d seconds",
                                        secs, usec );
                        }
                        if( copyout( (caddr_t) &secs, (caddr_t) arg,
                        sizeof(secs)))  {
                                goto BadCopy;
                        }
                }
                break;
        case RFM_TIMEOUT:               /* Set timeout period (usec)     */
                {
                        clock_t usec;   /* Timeout value in microseconds */
                        spelling = "RFM_TIMEOUT";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyin( (caddr_t) arg, (caddr_t) &usec,
                        sizeof(usec) ) ) goto BadCopy;
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb,
                                        "event timeout is now %d usec", usec );
                        }
                        ucb->ucb_eventWait = usec;
                }
                break;
        case RFM_GTIMEOUT:              /* Get timeout period (usec)     */
                {
                        spelling = "RFM_GTIMEOUT";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "event timeout is now %lu usec", 
                                        ucb->ucb_eventWait );
                        }
                        if( copyout( (caddr_t) &ucb->ucb_eventWait,
                        (caddr_t) arg, sizeof(ucb->ucb_eventWait)))     {
                                goto BadCopy;
                        }
                }
                break;
        case RFM_DMAINFO:
                {
                        rfmdmainfo_t    rdi;
                        int             bad;
                        spelling = "RFM_DMAINFO";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyin( (caddr_t) arg, (caddr_t) &rdi,
                        sizeof(rdi) ) ) goto BadCopy;
                        /* Validate the user's information               */
                        bad = 0;
                        if( rdi.rdi_burst > 64 )        {
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb,
                                "dma burst length limited to 64, not %d",
                                                rdi.rdi_burst );
                                }
                                ++bad;
                        }
                        switch( rdi.rdi_relmode )       {
                        default:
                                {
                                        WHENDEBUG( RFM_DBERROR )        {
                                                debugMsg( ucb, 
                                        "unknown bus release mode %d",
                                                        rdi.rdi_relmode );
                                        }
                                        ++bad;
                                }
                                break;
                        case RDI_RELMODE_ROR:
                        case RDI_RELMODE_RWD:
                        case RDI_RELMODE_ROC:
                        case RDI_RELMODE_BCAP:
                                break;
                        }
                        if( rdi.rdi_intrLeave > 15 )    {
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb, 
                                "dma interleave range is (0..15), not %d",
                                                rdi.rdi_intrLeave );
                                }
                                ++bad;
                        }
                        if( rdi.rdi_busreq > 3 )        {
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb,
                                        "VMEbus request level (%d) not 0..3",
                                                rdi.rdi_busreq );
                                }
                                ++bad;
                        }
                        if( !bad )      {
                                ucb->ucb_dmaInfo = rdi;
                                WHENDEBUG( RFM_DBIOCTL )        {
                                        dumpDmaInfo( ucb, &ucb->ucb_dmaInfo );
                                }
                                loadDmaInfo( ucb );
                        }
                }
                break;
        case RFM_GDMAINFO:
                {
                        spelling = "RFM_GDMAINFO";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        WHENDEBUG( RFM_DBIOCTL )        {
                                dumpDmaInfo( ucb, &ucb->ucb_dmaInfo );
                        }
                        if( copyout( (caddr_t) &ucb->ucb_dmaInfo,
                        (caddr_t) arg, sizeof(ucb->ucb_dmaInfo)))       {
                                goto BadCopy;
                        }
                }
                break;
        case RFM_CMD_INT:               /* Send interrupt to chassis     */
                {
                        char    cmd_data[2];
                        spelling = "RFM_CMD_INT";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyin( (caddr_t) arg, (caddr_t) &cmd_data,
                        sizeof(cmd_data) ) ) goto BadCopy;
                        switch( rfm->rfm_bid )  {
                        default:
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb, "unknown board ID=0x%X",
                                                rfm->rfm_bid );
                                }
                                ucb->ucb_errno = EINVAL;
                                break;
                        case RFM_5588_MAGIC:
                                /*FALLTHRU*/
                        case RFM_5578_MAGIC:
                                /*FALLTHRU*/
                        case RFM_5576_MAGIC:
                                rfm->rfm_cmn = cmd_data[1];
                                /*FALLTHRU*/
                        case RFM_5550_MAGIC:
                                rfm->rfm_cmd = cmd_data[0];
                                break;
                        }
                }
                break;
        case RFM_INTERRUPT:     /* Send interrupt to node(s)             */
                {
                        rfm_dnode_t rdn;        /* Holds node info       */
                        if( copyin( (caddr_t) arg, (caddr_t) &rdn,
                        sizeof(rdn) ) ) {
                                goto BadCopy;
                        }
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "sending event %d to node %d",
                                        rdn.rdn_channel, rdn.rdn_node );
                        }
                        /* Cannot send an interrupt to ourselves         */
                        if( rdn.rdn_node == ucb->ucb_mynodeid ) {
                                WHENDEBUG( RFM_DBERROR )        {
                                        debugMsg( ucb,
                                        "node %d cannot interrupt node %d",
                                                ucb->ucb_mynodeid,
                                                rdn.rdn_node );
                                }
                                ucb->ucb_errno = EINVAL;
                                goto Fini;
                        }
                        /* Validate the channel                          */
                        switch( rdn.rdn_channel )       {
                        default:
                                WHENDEBUG( RFM_DBIOCTL )        {
                                        debugMsg( ucb, "invalid channel %d",
                                                rdn.rdn_channel );
                                }
                                ucb->ucb_errno = EINVAL;
                                goto Fini;
                        case RFM_CHANNEL_A: break;
                        case RFM_CHANNEL_B: break;
                        case RFM_CHANNEL_C: break;
                        }
                        if( rdn.rdn_node == RFM_BROADCAST )     {
                                rdn.rdn_channel |= (1 << 6);
                                rdn.rdn_node = 0;
                        }
                        switch( ucb->ucb_bid )  {
                        default:
                                if(rdn.rdn_node < 0 || rdn.rdn_node > 15) {
                                        WHENDEBUG( RFM_DBIOCTL )        {
                                                debugMsg( ucb,
                                                        "invalid channel %d",
                                                        rdn.rdn_channel );
                                        }
                                        ucb->ucb_errno = EINVAL;
                                        goto Fini;
                                }
                                rfm->rfm_cmd = ((rdn.rdn_node << 2) |
                                        rdn.rdn_channel);
                                break;
                        case RFM_5576_MAGIC:/* VMIVME-5576               */
                                /* FALLTHRU                              */
                        case RFM_5578_MAGIC:/* VMIVME-5578               */
                                /* FALLTHRU                              */
                        case RFM_5588_MAGIC:/* VMIVME-5588               */
                                if(rdn.rdn_node < 0 || rdn.rdn_node > 255) {
                                        WHENDEBUG( RFM_DBIOCTL )        {
                                                debugMsg( ucb,
                                                        "invalid channel %d",
                                                rdn.rdn_channel );
                                        }
                                        ucb->ucb_errno = EINVAL;
                                        goto Fini;
                                }
                                rfm->rfm_cmn = rdn.rdn_node;
                                rfm->rfm_cmd = rdn.rdn_channel;
                                break;
                        }
                }
                break;
        case RFM_INTR_INIT:             /* Purge interrupts              */
                {
                        spelling = "RFM_INTR_INIT";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        setupHardware( ucb );
                }
                break;
        case RFM_INT_SENDER:            /* Return last interrupt sender  */
                {
                        spelling = "RFM_INT_SENDER";
                        WHENDEBUG( RFM_DBIOCTL )        {
                                debugMsg( ucb, "%s", spelling );
                        }
                        if( copyout( (caddr_t) &ucb->ucb_sender, (caddr_t) arg,
                        sizeof(ucb->ucb_sender)))       {
                                goto BadCopy;
                        }
                }
                break;
        case RFM_NOTIFY:                /* Reset the interrupt stuff     */
                {
                        rfm_event_t     eventInfo;/* Info to use         */
                        spelling = "RFM_NOTIFY";
                        if( copyin( (caddr_t) arg, (caddr_t) &eventInfo,
                        sizeof(eventInfo) ) )   {
                                goto BadCopy;
                        }
                        if( notificationControl( ucb, &eventInfo ) )    {
                                goto Fini;
                        }
                }
                break;
        case RFM_GNOTIFY:               /* Return notification status    */
                {
                        int             flag;
                        char            *eventName;
                        char            *state;
                        rfm_event_t     eventInfo;/* Info to use         */
                        spelling = "RFM_GNOTIFY";
                        if( copyin( (caddr_t) arg, (caddr_t) &eventInfo,
                        sizeof(eventInfo) ) )   {
                                goto BadCopy;
                        }
                        switch( eventInfo.event )       {
                        default:
                                {
                                        WHENDEBUG( RFM_DBIOCTL )        {
                                                debugMsg( ucb,
                                                        "unknown event code %d",
                                                        eventInfo.event );
                                        }
                                        ucb->ucb_errno = EINVAL;
                                }
                                goto Fini;
                        case RFM_EVENT_A:
                                {
                                        eventName = "A";
                                        flag = UCB_FLAGS_AINFO;
                                }
                                break;
                        case RFM_EVENT_B:
                                {
                                        eventName = "B";
                                        flag = UCB_FLAGS_BINFO;
                                }
                                break;
                        case RFM_EVENT_C:
                                {
                                        eventName = "C";
                                        flag = UCB_FLAGS_CINFO;
                                }
                                break;
                        case RFM_EVENT_F:
                                {
                                        eventName = "F";
                                        flag = UCB_FLAGS_FINFO;
                                }
                                break;
                        }
                        if( ucb->ucb_flags & flag )     {
                                state = "enabled";
                                eventInfo.sig =
                                        ucb->ucb_signal[eventInfo.event];
                        } else  {
                                state = "disabled";
                                eventInfo.sig = 0;
                        }
                        WHENDEBUG( RFM_DBINTR ) {
                                debugMsg( ucb,
                                        "event=%s(%d) signal=%d state=%s",
                                        eventName, eventInfo.event,
                                        eventInfo.sig, state );
                        }
                        if( copyout( (caddr_t) &eventInfo, (caddr_t) arg,
                        sizeof(eventInfo)) )    {
                                goto BadCopy;
                        }
                }
                break;
        }
Fini:
        retval = ucb->ucb_errno;
        WHENDEBUG( RFM_DBIOCTL )        {
                debugMsg( ucb, "%s complete (retval=%d)", spelling, retval );
        }
        topHalfUnlock( ucb );
        return( retval );
BadCopy:
        WHENDEBUG( RFM_DBIOCTL )        {
                debugMsg( ucb,
                        "bad ioctl(2) arg address for %s", spelling );
        }
        retval = ucb->ucb_errno = EFAULT;
        topHalfUnlock( ucb );
        return( retval );
}
/*
 *------------------------------------------------------------------------
 * rfm_map: called by kernel to service a mmap(2) system call
 *------------------------------------------------------------------------
 */
int
rfm_map(
        dev_t           dev,            /* Device to be mmap'ed          */
        vhandl_t        *vt,            /* Handle to caller's space      */
        off_t           off,            /* Beginning offset info region  */
        int             len             /* Length to map                 */
)
{
#if VMEIO
        vertex_hdl_t    vhdl = dev_to_vhdl(dev);
        UCB             ucb = device_info_get(vhdl);
        int             unit = ucb->ucb_unit;   /* for printing */
#else
        int             unit = RFMUNIT(dev);    /* Extract unit number   */
        UCB             ucb = &ucbs[unit];
#endif
        register RFM    rfm = ucb->ucb_rfm;
        caddr_t         addr;           /* Virtual address to map        */
        int             pva;            /* Process virtual address       */
        topHalfLock(ucb);
        /* Validate that the region is within the device                 */
        if( (off+len) > ucb->ucb_rfmSize )      {
                WHENDEBUG( RFM_DBMMAP ) {
                        debugMsg( ucb,
                        "rfmmap( ..., %d, %d, ... ) failed; rfm=%ld bytes",
                                len, off, (long) ucb->ucb_rfmSize );
                }
                topHalfUnlock(ucb);
                return( ENOMEM );
        }
        /* Compute origin address to map                                 */
        addr = ((caddr_t) ucb->ucb_rfm) + off;
        /* Attempt to map the region into the user's application         */
        if( (pva = v_mapphys( vt, addr, len )) )        {
                /* Failed for some region                                */
                WHENDEBUG( RFM_DBMMAP ) {
                        debugMsg( ucb, "v_mapphys( vt, 0x%X, 0x%X ) failed",
                                addr, len );
                }
                topHalfUnlock(ucb);
                return( ENOMEM );
        }
        /* Ok, the region is yours...                                    */
        WHENDEBUG( RFM_DBMMAP ) {
                debugMsg( ucb, "mapped %d bytes at offset %d to 0x%X", len,
                        off, pva );
        }
        ucb->ucb_errno = 0;
        topHalfUnlock(ucb);
        return( pva );
}
/*
 *------------------------------------------------------------------------
 * rfm_unmap: do any local cleanup for the munmap(2) system call
 *------------------------------------------------------------------------
 * Although this routine can be called concurrently, it doesn't touch any
 * shared data, so there is no need to lock it.
 *------------------------------------------------------------------------
 */
int
rfm_unmap(
        dev_t           dev,            /* Device number                 */
        vhandl_t        *vt             /* Mapped address (ignored)      */
)
{
        WHENDEBUG( RFM_DBMMAP ) {
                debugMsg( UNULL, "unmapped" );
        }
        return( 0 );                    /* Nothing to it, really!        */
}
/*
 *------------------------------------------------------------------------
 * rfm_unload: release resources and prepare to be removed from memory
 *------------------------------------------------------------------------
 * This routine cannot be called concurrently.
 *------------------------------------------------------------------------
 */
int
rfm_unload(
        void
)
{
        register UCB    ucb;            /* Per-device info               */
        register UCB    lucb;           /* Last info + 1                 */
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( UNULL, "checking for open devices" );
        }
        for( ucb = ucbs, lucb = ucb+NRFM; ucb < lucb; ++ucb )   {
                if( ucb->ucb_flags & UCB_FLAGS_OPEN )   {
                        /* Someone is still open, so bail out            */
                        WHENDEBUG( RFM_DBERROR )        {
                                debugMsg( ucb, "still busy; cannot unload" );
                        }
                        return( 1 );
                }
        }
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( UNULL, "checking pending timeouts" );
        }
        for( ucb = ucbs, lucb = ucb+NRFM; ucb < lucb; ++ucb )   {
                toid_t          tid;    /* ID of existing timeout        */
                disableRfmInterrupts( ucb );
                if( (tid = ucb->ucb_eventTimeoutId) )   {
                        ucb->ucb_eventTimeoutId = NULL;
                        untimeout( tid );
                        WHENDEBUG( RFM_DBINIT ) {
                                debugMsg( ucb, "discarded timeout" );
                        }
                }
        }
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( UNULL, "destroying mutex's" );
        }
        for( ucb = ucbs, lucb = ucb+NRFM; ucb < lucb; ++ucb )   {
                WHENDEBUG( RFM_DBINIT ) {
                        debugMsg( UNULL, "destroying mutex" );
                }
                MUTEX_DESTROY( (mutex_t *) &ucb->ucb_mutex );
        }
        /* Release VME interrupt vector                                  */
        WHENDEBUG(RFM_DBINIT)   {
                debugMsg( UNULL, "freeing VMEbus interrupt vectors" );
        }
        for( ucb = ucbs, lucb = ucb+NRFM; ucb < lucb; ++ucb )   {
                if( ucb->ucb_flags & UCB_FLAGS_FOUND )  {
                        WHENDEBUG( RFM_DBINIT ) {
#if VMEIO
                                debugMsg( ucb,
                                          "freeing adapter=%v, vector=0x%X",
                                        ucb->ucb_vertex, ucb->ucb_ivec );
#else
                                debugMsg( ucb,
                                        "freeing adapter=%d, vector=0x%X",
                                        ucb->ucb_adapter, ucb->ucb_ivec );
#endif p
                        }
#if VMEIO
                        vmeio_intr_free(ucb->ucb_intr);
#else
                        vme_ivec_free (ucb->ucb_adapter, ucb->ucb_ivec );
#endif
                        ucb->ucb_flags &= ~UCB_FLAGS_FOUND;
                }
        }
        /* Unmap device registers                                        */
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( UNULL, "unmapping registers" );
        }
        for( ucb = ucbs, lucb = ucb+NRFM; ucb < lucb; ++ucb )   {
                if( ucb->ucb_piomap )   {
                        WHENDEBUG( RFM_DBINIT ) {
                                debugMsg( ucb, "freeing register map" );
                        }
#if VMEIO
                        vmeio_piomap_free(ucb->ucb_piomap);
#else
                        pio_mapfree( ucb->ucb_piomap );
#endif 
                        ucb->ucb_rfm = 0;
                        ucb->ucb_piomap = 0;
                }
        }
        return( 0 );
}
/*
 *------------------------------------------------------------------------
 * rfm_halt: system is about to halt
 *------------------------------------------------------------------------
 * This routine cannot be called concurrently.
 *------------------------------------------------------------------------
 */
void
rfm_halt(
        void
)
{
        WHENDEBUG( RFM_DBINIT ) {
                debugMsg( UNULL, "halted" );
        }
}