The Peripheral Component Interconnect (PCI) bus, initially designed at Intel Corp, is standardized by the PCI Bus Interest Group, a nonprofit consortium of vendors (see “Standards Documents” and “Internet Resources”).
The PCI bus is designed to be a high-performance local bus to connect peripherals to memory and a processor. In many personal computers based on Intel and Motorola processors, the primary system bus is a PCI bus. A wide range of vendors make devices that plug into the PCI bus.
The PCI bus is supported by the O2 workstation as well as other SGI systems. However, the O2 was the first SGI system to support PCI, and IRIX 6.3 was the first release of IRIX to contain PCI bus support. This chapter contains the following topics related to support for the PCI bus:
“PCI Bus in Silicon Graphics Workstations” gives an overview of PCI bus features and implementation.
“PCI Implementation in O2 Workstations” describes the hardware features and restrictions of one PCI implementation.
“Driver/Kernel Interface for PCI Access” discusses the kernel functions that are specifically designed to support PCI device drivers.
“Example Driver” displays the code of a complete PCI driver.
A PCI driver is a kernel-level device driver like other drivers. For information on the architecture of a kernel-level device driver and on how to build and debug one, see Part III, “Kernel-Level Drivers.”
This section contains an overview of the main features of PCI hardware attachment, for use as background material for software designers. Hardware designers can obtain a detailed technical paper on PCI hardware through the Silicon Graphics Developer Program. Design issues such as power supply capacities, card dimensions, signal latencies, and arbitration, are covered in that material.
In no Silicon Graphics system is the PCI bus the primary system bus. The primary system bus is always a proprietary bus that connects one or more CPUs with high-performance graphics adapters and main memory. The PCI bus adapter is connected (or “bridged,” in PCI terminology) to the system bus, as shown in Figure 15-1.
The PCI adapter is a custom circuit with these main functions:
To act as a PCI bus target when a PCI bus master requests a read or write to memory
To act as a PCI bus master when a CPU requests a PIO operation
To manage PCI bus arbitration, allocating bus use to devices as they request it
To interface PCI interrupt signals to the system bus and the CPU
Different SGI systems have different PCI adapter ASICs. Although all adapters conform to the PCI standard level 2.1, there are significant differences between them in capacities, in optional features such as support for the 64-bit extension, and in performance details such as memory-access latencies.
A system may contain one or more PCI bus adapters. Each bus connects one or more physical packages. The PCI standard allows up to 32 physical packages on a bus. A “package” may consist of a card plugged into a slot on the bus. However, a “package” can also consist of an internal chipset mounted directly on the system board, using the PCI bus and occupying one or more virtual slots on the bus. For example, the SCSI adapter in the O2 workstation occupies the first two virtual slots of the PCI bus in that system.
Each physical package can implement from one to eight functions. A PCI function is an independent device with its own configuration registers in PCI configuration space, and its own address decoders.
In Silicon Graphics systems, each PCI function is integrated into IRIX as a device. A PCI device driver manages one or more devices in this sense. A driver does not manage a particular package, or card, or bus slot; it manages one or more logical devices.
In the O2 workstation, a proprietary system bus connects the CPU, multimedia devices (audio, video, and graphics) and main memory.
The PCI bus adapter interfaces one PCI bus to this system bus. The PCI bus adapter is a unit on the system bus, on a par with the other devices. The PCI bus adapter competes with the CPU and with multimedia I/O for the use of main memory.
The built-in SCSI adapter, which is located on the main system board, is logically connected to the PCI bus and takes the place of the first two “slots” on the PCI bus, so that the first actual slot is number 2.
In the O2, the PCI adapter implements a standard, 32-bit PCI bus operating at 33 MHZ. The following optional signal lines are not supported.
The LOCK# signal is ignored; atomic access to memory is not supported.
The cache-snoop signals SBO# and SDONE are ignored.
The JTAG signals are not supported.
The O2 PCI adapter supports 64-bit data transfers, but not 64-bit addressing. All bus addresses are 32 bits, that is, all PCI bus virtual addresses are in the 4 GB range. The Dual Address Cycle (DAC) command is not supported (or needed).
The 64-bit extension signals AD[63:32], C/BE#[7:4], REQ64# and ACK64# are pulled up as required by the PCI standard.
When the PCI bus adapter operates as a bus master (as it does when implementing a PIO load or store for the CPU), the PCI adapter generates 32-bit data cycles.
When the PCI bus adapter operates as a bus target (as it does when a PCI bus master transfers data using DMA), the PCI adapter does not respond to REQ64#, and hence 64-bit data transfers are accomplished in two, 32-bit, data phases as described in the PCI specification.
When the IRIX kernel probes the PCI bus and finds an active device, it initializes the device configuration registers as follows:
Command Register | The enabling bits for I/O Access, Memory Access, and Master are set to 1. Other bits, such as Memory Write and Invalidate and Fast Back-to-Back are left at 0. |
Cache Line Size | 0x20 (32, 32-bit words, or 128 bytes). |
Latency Timer | 0x30 (48 clocks). |
Base Address registers | Each register that requests memory or I/O address space is programmed with a starting address. In the O2 system, memory addresses are always greater than 0x8000 0000. |
The device driver is free to set any other configuration parameters in the pfxattach() entry point (see “Attaching a Device”).
The relationship between the PCI bus address space and the system memory physical address space differs from one system type to another.
The O2 workstation and related systems support a 1 GB physical memory address space (30 bits of physical address used). Any part of physical address space can be mapped into PCI bus address space for purposes of DMA access from a PCI bus master device. The device driver ensures correct mapping through the use of a DMA map object (see “Allocating DMA Maps”).
Physical memory can be mapped to the PCI bus in normal order or byte-swapped order. Byte-swapping is done on the basis of 64-bit units. When a PCI bus master uses a byte-swapped DMA address as its target, and writes the 64-bit data item 0x0807 0605 0403 0201, the data 0x0102 0304 0506 0708 is delivered to memory.
For PIO purposes (the CPU loading and storing in device space), memory space defined by each PCI device in its configuration registers is allocated in the upper two gigabytes of the PCI address space, above 0x8000 0000. These addresses are allocated dynamically, based on the contents of the configuration registers of active devices. The I/O address space requested by each PCI device in its configuration registers is also allocated dynamically as the system comes up.
It is possible for a PCI device to request (in the initial state of its Base Address Registers) that its address space be allocated in the first 1 MB of the PCI bus. This request cannot be honored in the O2 workstation. Devices that cannot decode bus addresses above 0x8000 0000 are not supported.
Device drivers get a virtual address to use in PIO access by creating a PIO map (see “Managing PIO Maps for PCI”).
Two devices that are built in to the workstation take the positions of PCI bus slots 0 and 1. Actual bus slots begin with slot 2 and go up to the maximum for the system (just the one slot in O2).
The PCI adapter maintains two priority groups. The lower-priority group is arbitrated in round-robin style. The higher-priority group uses fixed priorities based on slot number, with the higher-numbered slot having the higher fixed priority.
The IRIX kernel assigns slots to priority groups dynamically by storing values in an adapter register. There is no kernel interface for changing this priority assignment. The audio and the available PCI slots are in the higher priority group.
The PCI adapter can present eight unique interrupt signals to the system CPU. The IRIX kernel uses these interrupt signals to distinguish between the sources of PCI bus interrupts. The system interrupt numbers 0 through 7 are distributed across the PCI bus slots as shown in Table 15-1 (“n.c.” means no connection).
Table 15-1. PCI Interrupt Distribution to System Interrupt Numbers
PCI Interrupt | Slot 0 (built-in device) | Slot 1(built-in device) | Slot 2 | Slot 3 | Slot 4 |
|---|---|---|---|---|---|
INTA# | system 0 | n.c. | system 2 | system 3 | system 4 |
INTB# | n.c. | system 1 | system 5 | system 7 | system 6 |
INTC# | n.c. | n.c. | system 6 | system 5 | system 7 |
INTD# | n.c. | n.c. | system 7 | system 6 | system 5 |
Each physical PCI slot has a unique system interrupt number for its INTA# signal. The INTB#, INTC#, and INTD# signals are connected in a spiral pattern to three system interrupt numbers.
A PCI device driver manages the operation of one or more devices. In this section, “device” has two meanings.
A device can be a function associated with one set of configuration registers on a PCI card. A PCI card can contain up to eight such functions, but each configuration space is treated as a separate device by IRIX..
A logical device is a device special file in the /dev filesystem that refers to the original PCI device. For example, a PCI card that contains a serial port might be associated with /dev/ttyd12, /dev/ttyf12, and /dev/ttym12.
Besides the usual driver entry points for a block or character driver, a PCI driver has to supply the pfxattach() entry point. This entry point is called to initialize the PCI device and, optionally, to identify any additional logical devices for that PCI device. An entry point named pfxdetach() is optional.
Besides the usual DDI/DKI functions, the PCI driver calls on kernel functions unique to the PCI bus. These functions are introduced in the following topics. For a summary, see “PCI Function Summary”.
A PCI driver is a kernel-level device driver that has the general structure described in Chapter 8, “Structure of a Kernel-Level Driver.” It uses the driver/kernel interface described in Chapter 9, “Device Driver/Kernel Interface.” A PCI driver can be loadable or it can be linked with the kernel. In general it is configured into IRIX as described in Chapter 10, “Building and Installing a Driver.”
PCI hardware configuration is more dynamic than the configuration of the VME, EISA or SCSI buses. With other types of bus, the driver learns the hardware configuration when the driver is loaded, and the configuration remains static afterward. IRIX support for the PCI bus is designed to allow support for dynamic reconfiguration in future systems. In principle, a PCI driver has to be designed to allow devices to be attached and detached at any time (no detaching is done in the current release).
The general sequence of operations of a PCI driver is as follows:
In the pfxinit() entry point, the driver calls a kernel function to register itself as a PCI driver, specifying the kind of device it supports.
When the kernel discovers a device of this type, it calls the pfxattach() entry point of the driver. The driver creates PIO maps and (optionally) DMA maps to use in addressing the device; initializes the device; and if necessary, registers an interrupt handler.
In the normal upper-half entry points such as pfxopen(), pfxread(), and pfxstrategy(), the driver operates the device and transfers data.
When the device generates an interrupt, the driver's registered interrupt handler is called.
If the kernel learns of a bus address error on the PCI bus, it can call an error-handling function registered by the driver to find out which device caused the error.
Conceptually, if the kernel learns that the device is being detached, the kernel calls the driver's pfxdetach() entry point. The driver notes the device is unusable and stops servicing it through upper-half entry points. (This feature is not implemented in the current release.)
As described under “Driver Flag Constant”, the pfxdevflag public name is a byte containing flags for driver characteristics. Since a PCI driver is inevitably a new driver, with no heritage in older versions of IRIX or UNIX, Silicon Graphics, Inc. strongly recommends that you design it from the start to be compatible with multiprocessors. The implications of this are discussed under “Planning for Multiprocessor Use”.
A PCI driver must register with the kernel in order to receive notification that devices exist. In the current release this is done in two stages. First the driver calls pciio_add_attach() to introduce itself to the kernel. See reference page pciio(d3) for the function prototype. The argments passed in this call are as follows:
attach | Address of the driver's pfx attach() entry point. This is required. |
detach | NULL, or address of the driver's pfx detach() entry point if implemented. |
error | NULL, or address of the driver's error-handling function if any. |
driver_prefix | Pointer to the driver's prefix as a character string. |
major | The major number supported by this driver. The number given in the third field of the descriptive line (see “Descriptive Line”). |
This call associates the driver's pfxattach() entry point with the driver's prefix string. The pfxdetach() and error-handler addresses may be passed as NULL when these are not implemented. There is no need to implement pfxdetach() in IRIX 6.3.
![]() | Note: This call to pciio_add_attach() is unique to IRIX 6.3; it is not required or allowed in subsequent releases. You can compile the call conditionally based on the definition of the variable _EARLY_PCI, which is not defined in later releases—as demonstrated in Example 15-1. |
![]() | Tip: You can create a static list of major numbers as a global variable in the driver descriptive file. See “Variables Section” for an example of such an array. |
The next step—and the only step required in later releases—is to call the pciio_driver_register() function (see reference page pciio(d3) for the syntax). This call specifies the PCI vendor ID and device ID numbers as they appear in the PCI configuration space of any device that this driver supports. The third argument is a character string containing the driver's prefix string (as passed to pciio_add_attach()). The kernel uses this string to search the switch tables to find the addresses of the driver's pfxattach() and pfxdetach() entry points.
Example 15-1 shows a hypothetical example of driver registration.
__int32_t hypo_attach(dev_t); /* forward declaration */
int hypo_init()
{
int allMyMajors[2];
...
allMyMajors[0] = myMajNum; /* see Example 10-1 */
allMyMajors[1] = 0;
#ifdef _EARLY_PCI /* need to call pciio_add_attach */
ret = pciio_add_attach(hypo_attach, /* attach entry point */
NULL,NULL, /* detach, error not done */
"hypo_", /* prefix string */
allMyMajors); /* list of one major# */
if (!ret) ...
#endif
ret = pciio_driver_register(HYPO_VENDOR,HYPO_DEVID,"hypo_",0);
if (!ret) ...
}
|
A device driver can register multiple times to handle multiple combinations of vendor ID and device ID.
![]() | Tip: You should defer the call to pciio_driver_register() to the end of the pfxinit() routine, when all global data has been initialized. The reason is that, if there is an available device of the specified type, pfxattach() might be called immediately, before the pci_driver_register() function returns. In a multiprocessor, pfxattach() can be called concurrently with the return of pci_driver_register() and following code. |
A loadable driver, when called at its pfxunload() entry point, can unregister before unloading; but that is not required. (See “Unloading”).
The IRIX support for PCI is designed to allow for future support for hot-swapping and for multinode systems in which devices, slots, buses, and whole nodes come online and offline dynamically. In principle, a PCI device can be attached or detached at any time, meaning that the pfxattach() and pfxdetach() entry points could be called at any time.
In practice, the only systems supported by IRIX 6.3 for O2 do not permit hot-swapping. Also, the administrator commands that would force a device to detach are not defined as yet. In current systems, pfxattach() is only called at boot time and pfxdetach() is not called. Nevertheless the driver/kernel interface is designed for future flexibility. For future portability you can design your driver as if that flexibility existed now.
When the system boots up, the kernel probes the PCI bus configuration space and takes a census of active devices. For each device it notes
Vendor and device ID numbers
Requested size of memory space
Requested size of I/O space
The kernel assigns starting bus addresses for memory and I/O space and sets these addresses in the Base Address Registers (BARs) in the device. Then the kernel looks for a driver that has registered a matching set of vendor and device IDs using pciio_driver_register().
If no matching driver has registered, the device remains inactive. For example, the driver might be a loadable driver that has not been loaded as yet. When the driver is loaded and registers, the kernel will match it to any unattached devices.
When the kernel matches a device to its registered driver, the kernel calls the driver's pfxattach() entry point. It passes one argument, a vertex_hdl_t, which acts as an opaque handle to a kernel object that describes this device. This handle is used to:
Store and retrieve the driver's private information about the device
Request PIO and DMA maps on the device
Register an interrupt handler for the device
A driver needs to save information about each device, usually in a structure. Fields in a typical structure might include:
Locks or semaphores used for mutual exclusion among upper-half entry points and between them and the interrupt handler.
Addresses of allocated PIO and DMA maps for this device (see “Allocating PIO Maps”).
Address of an interrupt connection object for the device (see “Registering an Interrupt Handler”).
In a block driver, anchors for a queue of buf_t objects being filled or emptied.
Device status information and flags.
A problem is that at initialization time a driver does not know how many devices it will be asked to manage. For a workstation such as O2 you can expect the number will be small, but you should allow for portability to server-class systems that support dozens of devices. In the past this problem has been handled by allocating an array of a fixed number of information structures, indexed by the device minor number.
This is not a good solution for a PCI driver because PCI configuration is so dynamic. In addition, a loadable driver loses the contents of its global variables when it unloads. The IRIX PCI support gives you a different way.
In a PCI driver, you dynamically allocate memory for an information structure to hold information about the one device being attached. (See “General-Purpose Allocation”.) You save the address of the structure in the kernel's hardware vertex, using the device_info_set() function, which associates an arbitrary pointer with a vertex_hdl_t.
extern void device_info_set(vertex_hdl_t, arbitrary_info_t); |
The information structure can easily be recovered in any top-half routine; see “Locating Device Information”.
For almost any device you need at least one PIO map. You use a PIO map to read the memory space or the I/O space of a device. These maps can be allocated when the device is attached, and the addresses of the maps can be stored in the device information structure.
A PIO map is created by pciio_piomap_alloc(). See reference page pciio_pio(d3) for the syntax. This call requires arguments are as follows:
vhdl | The vertex_hdl_t received by the pfx attach() routine. Every map must be associated to a specific device at its hardware graph vertex. |
desc | Device descriptor structure with one field set (see text following). |
space | Constant specifying the space to map: PCIIO_SPACE_CFG (configuration space), or PCIIO_SPACE_WIN(n). |
addr | Offset within the selected space (typically 0). |
size | Span of the total area over which this map might be applied. |
max | Maximum size of the area that will be mapped at any one time. When the map is always used for the same area, size and max are the same. When the map can be used for smaller segments within a larger area, max is the limit of one segment and size the size of the total extent. |
flags | Passed as 0. |
A PIO map that you will use to access device configuration registers is based on a a space of PCIIO_SPACE_CFG. The space selection PCIIO_SPACE_WIN(n) means that this map is to be based on Base Address register n, from 0 through 5, in the PCI configuration space. The device configuration registers specify whether a given base address register defines memory or I/O space. When the space is defined by a 64-bit base address register, use the lower number, the index of the word that contains the configuration bits.
![]() | Tip: The header file sys/PCI/pciio.h declares constants of the form PCIIO_PIOMAP_CFG and PCIIO_PIOMAP_WIN(n). You can ignore these; they are not used in any calls. The target space for any kind of map is given with PCIIO_SPACE_*. |
The device descriptor structure type dev_desc_t is declared in pciio.h. A descriptor structure is required in this call, but only one field is inspected, intr_swlevel. It must be set to one of the interrupt levels of type pl_t as declared in ddi.h, typically plhi, as shown in Example 15-2
![]() | Note: In subsequent releases the device descriptor is not required and the address can be passed as NULL; but for this release it is required. |
Example 15-2 shows a function that simplifies the allocation of a PIO map. The space is passed as an argument, as is the size of the space to map. The function makes the simplifying assumption that the map should start at offset 0 in the selected space.
pciio_piomap_t makeMap(vertex_hdl_t dev, int base, size_t size)
{
struct device_desc_s ddesc;
ddesc.intr_swlevel = plhi;
return pciio_piomap_alloc(
dev, /* vertex handle */
&ddesc, /* dev descriptor w/ in level in it */
base, /* space, _CFG or _WIN(n) */
0, /* starting offset */
size,size, /* size to map */
0); /* default endian */
}
|
In the O2 and some other platforms, PIO addressing is based on fixed hardware resources and a PIO address can be generated without use of a map. You request this using the function pciio_piotrans_addr(). In the general case of a PIO address for memory or I/O space, this function call can fail in some systems (as discussed in reference page pciio_pio(d3)). When used to obtain a PIO address for configuration space, it generally succeeds in all systems.
For a bus-master device you will need at least one DMA map. A DMA map is created by pciio_dmamap_alloc(), which takes a vertex_hdl_t, a size, and flags regarding the treatment of the mapping. (See reference page pciio_dma(d3) for the syntax of this and related functions.)
More map functions are discussed under “Managing PIO Maps for PCI” and “Managing DMA Maps for PCI”.
Typically a PCI driver needs to read the device configuration registers and possibly write to them. In principle, these are PIO operations. However, in the O2 (and possibly other platforms), the special PCI bus cycle called a Configuration cycle is not generated by a simple PIO load or store.
To access the configuration, create a PIO map for the configuration space and extract an address from it. Present this address to pciio_config_get() to fetch a word from configuration space, as shown in Example 15-3. (See reference page pciio_config(d3) for the syntax of this and related functions.)
The function in Example 15-3 fetches and returns a 32-bit word from configuration space. It obtains a PIO base address for configuration space using pciio_piotrans_addr() (see “Allocating PIO Addresses Directly”). When that call succeeds, as it does in the O2 and other current SGI systems, the address is passed to pciio_config_get().
__uint32_t get_cfg_word( vertex_hdl_t vh, int reg)
{
device_desc_t dd = {0};
volatile __uint32_t *pio_addr;
dd.intr_swlevel = plhi;
pio_addr = pciio_piotrans_addr(vh, dd,
PCIIO_SPACE_CFG,0,256,0);
if (pio_addr) /* trans_addr succeeded */
return pciio_config_get(pio_addr,reg);
else /* trans_addr failed, simulate hardware failure */
return (__uint32_t)(-1);
}
|
For a PCI bus master device, the pfxattach() function should set the Cache Line Size register to 128 (the size of a cache line in all Silicon Graphics systems).
For devices that can interrupt, a key step during pfxattach() is to register an interrupt handler for the device. This is done in a two-step process. First you create an interrupt connection object; then you use that object to specify the interrupt handling function.
The interrupt connection is created with pciio_intr_alloc(), which takes a vertex_hdl_t and a flag for the interrupt line that the device uses. (See reference page pciio_intr(d3) for the syntax.)
The interrupt object is used in establishing a handler, and it is needed later to stop taking interrupts (see “Inactivating an Interrupt Handler”). You probably want to save its address in the device information structure for later use.
After creating the interrupt object, you establish a handler using pciio_intr_connect(). Its principal arguments are the interrupt object, a handler address, and a value to be passed to the handler when it is called—typically the address of the device information structure you are preparing. If a device will interrupt on line C, interrupt setup could resemble Example 15-4.
pciio_intr_t intobj;
extern void int_handler(eframe_t *, void *);
int retcode;
intobj = pciio_intr_alloc(
vhdl, /* as received in attach() */
0, /* device descriptor is n.a. for pci */
PCIIO_INTR_LINE_C, /* the line it uses */
(vertex_hdl_t) 0);
retcode = pciio_intr_connect(
intobj, /* the interrupt object */
(intr_func_t) int_handler, /* the handler */
(intr_arg_t) pDevInfo, /* dev info as input */
(void*)0 ); /* threads are next release */
if (!retcode) cmn_err(CE_WARN,"oh fiddlesticks");
|
![]() | Note: The declaration of the interrupt handler function type, intr_func_t, requires two arguments, the first being an eframe_t. The availability of the eframe_t is unique to IRIX 6.3 for O2. In subsequent releases the interrupt handler receives only one argument, the value passed with the pciio_intr_connect() call. |
The CPU is accepting interrupts when the pfxattach() entry point is called. If the PCI device is in a state that can produce an interrupt, the interrupt handling function can be called before pciio_intr_connect() returns. Make sure that all global data used by the interrupt handler has been initialized.
PCI devices can share the four PCI interrupt lines. As a result, in some cases the kernel cannot tell which device caused an interrupt. When there is any doubt, the kernel calls all the interrupt handlers that are registered to that interrupt line. For this reason, your interrupt handler must not assume that its device did cause the interrupt. It should always test to see if an interrupt is really pending, and exit immediately when one is not.
The return code from pfxattach() is tested by the kernel. The driver can reject an attachment. When your driver cannot allocate memory, or fails due to another problem, it should:
Use cmn_err() to document the problem (see “Using cmn_err”)
Release any objects such as PIO and DMA maps that were created.
Release any space allocated to the device such as a device information structure.
Return an informative return code which might be meaningful in future releases.
More than one driver can register to support the same vendor ID and device ID. When the first driver fails to complete the attachment, the kernel continues on to test the next, until all have refused the attachment or one accepts it. The pfxdetach() entry point can only be called if the pfxattach() entry point returns success (0).
Some kinds of physical devices are represented by multiple device special files in /dev. For example, each serial port appears as at least four devices /dev/tty*. A tape drive can appear under different names, and a disk device has two device special files for each disk partition, one in /dev/dsk and one in /dev/rdsk (raw, or character access). Each logical device represents a slightly different treatment of the same physical device.
The pfxattach() entry point initializes the real PCI device, but it must also create hardware vertexes to represent the logical devices that should be associated with the same real PCI device. This is done in three steps:
Create a new hardware vertex connected to the attached vertex, using hwgraph_device_add().
Associate the new vertex with the minor number of the logical device, using hwgraph_device_add_minor().
Associate the new vertex with the same device information structure, using device_info_set().
The hwgraph_device_add() function has the following prototype:
int hwgraph_device_add(vertex_hdl_t vhdl, /* parent vertex */
char *name, /* name of the device */
char *prefix, /* driver prefix */
vertex_hdl_t *new_vrtx) /* return result */
|
The name argument is not significant in the current release, but it will be significant and visible to users in a future release. It should be one word or numeric characters to label this vertex of the hardware graph, for example “0” (logical unit number) or “nonswap” (feature or access method).
For a simplified example, see Example 15-5. This hypothetical code, which would be part of the hypo_attach() entry point, creates two logical devices. The device minor number of the first is 0x01; the second is 0x02—a simplified version of the conventions for minor numbers of tape or serial devices, in which the minor number bits represent device options or features.
vertex_hdl_t subdev;
int ret;
my_dev_info_t *pDev; /* struct stored in PCI vertex */
...
ret = hwgraph_device_add(vhdl, /* attach() input */
"left", /* name of minor 01 */
"hypo_", /* driver prefix */
&subdev); /* output here */
if (ret)...
ret = hwgraph_device_add_minor(subdev,(minor_t)0x01);
if (ret)...
device_info_set(subdev,my_dev_info);
ret = hwgraph_device_add(vhdl, /* attach() input */
"right", /* name of minor 02 */
"hypo_", /* driver prefix */
&subdev); /* output here */
if (ret)...
ret = hwgraph_device_add_minor(subdev,(minor_t)0x02);
if (ret)...
device_info_set(subdev,my_dev_info);
|
While handling normal operations on the device, the driver needs to locate device information from the top-half entry points, and needs to translate addresses using maps.
The driver upper-half entry points are called to implement requests from user processes or filesystems that need to open, read, write, map or control the device. These calls can occur at any time; and on a multiprocessor, they can occur multiple times concurrently, on parallel CPUs.
The user process refers to a device through a file descriptor opened to a device special file. The primary argument to any upper-half entry point is the dev_t, a value that distinguishes the device. Traditional drivers extract device numbers from the dev_t (see “The Device Number Types”).
The first thing any upper-half entry point needs to do is to locate the per-device information structure that was prepared in the pfxattach() entry point (see “Allocating Storage for Device Information”). You do this by calling device_info_get(). However, that function takes a vertex_hdl_t. You get that from dev_to_vhdl(). The code, which is repeated over and over in a PCI driver, resembles Example 15-6.
vertex_hdl_t vhdl = dev_to_vhdl(dev); my_dev_info_t *pDev = (my_dev_info_t)device_info_get(vhdl); if (!pDev) return(ENXIO); if (!(pDev->status & USABLE)) return(ENXIO); |
In the pfxopen() entry point, the dev_t is received as a reference argument, not by value.
In the event that device_info_get() returns NULL, this device has not been attached, or the pfxattach() entry point did not allocate an information structure; or the pfxdetach() entry point has been called. In such cases, the upper-half routine should return ENXIO (no such device). This test is shown in Example 15-6.
Example 15-6 also shows another test. In a future release of IRIX, a driver will be able to implement a pfxdisable() entry point, called to make a device temporarily unusable. Even in the current release, your driver might find reasons, such as a persistent device error, to force a device offline. A single flag bit in the device information structure represents this state. Again, a return of ENXIO is appropriate.
The functions that are used to manage PIO maps are summarized in Table 15-2. See reference page pciio_pio(d3) for details.
Table 15-2. Functions for PIO Maps for PCI
Function | Purpose and Operation |
|---|---|
pciio_piomap_addr() | Get a kernel virtual address from a PIO map for a specific offset and length. |
pciio_piomap_alloc() | Create a PIO map object, specifying the bus address space, base offset, and length it needs to cover. |
pciio_piomap_done() | Make a PIO map inactive until it is next needed (may release hardware resources asslociated to the map). |
pciio_piomap_free() | Release a PIO map object. |
pciio_piotrans_addr() | Request immediate translation of a bus address to a kernel virtual address without use of a PIO map. Returns NULL if this system does not support fixed PIO addressing for the requested space. |
pciio_config_get() | Fetch a 32-bit value from configuration space using an address returned by pciio_piomap_addr(). |
pciio_config_set() | Store a 32-bit value into configuration space using an address returned by pciio_piomap_addr(). |
Maps are allocated with pciio_piomap_alloc(). Its use is covered under “Allocating PIO Maps”, because you typically will allocate the PIO maps you need while attaching the device.
You use a PIO map by calling pciio_piomap_addr(). This function takes a map, an offset within the PCI address space described by the map, and the number of bytes of space that the address will be used to retrieve.
The pciio_addr argument is added to the base offset specified to pciio_piomap_alloc(), and that in turn is added to the base address assigned by the kernel to this device, to arrive at the bus address needed. The byte_count argument specifies how many bytes beyond the bus address you may access. The returned value is a kernel virtual address that is mapped to the requested PCI bus address for at least that many bytes.
![]() | Tip: A program variable based on a PIO address should always be declared as “volatile.” |
Once you have extracted an address using pciio_piomap_addr(), the map is active. It remains active until you call either pciio_piomap_done() or pciio_piomap_free(). In some systems, it costs nothing to keep a PIO map active. In other systems, an active PIO map may tie up global hardware resources. It is is a good idea to call pciio_piomap_done() when the address is no longer needed.
Some systems also support a one-step translation function, pciio_piotrans_addr(), as described under “Allocating PIO Addresses Directly”. However, this function can fail in systems that do not use hard-wired bus maps. The two-step process of allocating a map and then interrogating it is more general.
pciio_piotrans_addr() always succeeds when getting a PIO address in configuration space. Access to configuration space is done in two steps. First you get a PIO address; then you pass the address to pciio_config_get() or pciio_config_get(). An example is shown under “Reading the Device Configuration”..
When you use PIO to fetch or store 32-bit values on 32-bit-aligned PCI addresses, PIO works as you would expect, and a 32-bit value is fetched or returned.
However, when you use PIO to fetch or store less than 32 bits—either a 16-bit value or an 8-bit value—you must use an address that takes account of byte-swapping. The least significant address bits are summarized in Table 15-3.
Table 15-3. Least Significant Address Bytes for Short PIO
Binary Offset Within 32-bit Memory Word | LSB for 16-Bit Access | LSB for 8-bit Access |
|---|---|---|
0x00 | 0x02 | 0x03 |
0x01 | n.a. | 0x02 |
0x02 | 0x00 | 0x01 |
0x03 | n.a. | 0x00 |
You can deal with this complication in either of three ways, as follows:
Always fetch and store 32-bit words. Unpack smaller units in memory.
Declare the device data as a structure and arrange the order of short fields in the structure so that the least significant address bits work out correctly. For example if the device offers the following logical structure in its PCI memory space:
00--> dma base addr, 4 bytes 04--> dma counter, 4 bytes 08--> control reg, 2 bytes 0A--> status reg, 1 byte 0B--> byte fifo, 1 byte |
Declare this as a C structure as follows:
struct {
unsigned dma_addr;
unsigned dma_count
unsigned char byte_fifo;
unsigned char status;
unsigned short control
}
|
Write C macros for byte-address and halfword-address. The macros would use exclusive-OR to invert two or one (respectively) of the least-significant bits.
The functions that are used to manage simple DMA maps are summarized in Table 15-4. See reference page pciio_dma(d3) for syntax.
Table 15-4. Functions for Simple DMA Maps for PCI
Function | Purpose and Operation |
|---|---|
pciio_dmamap_alloc() | Create a DMA map object, specifying the maximum extent of memory the map will have to cover. |
pciio_dmamap_addr() | Get the bus virtual address corresponding to a memory address for a specified length. |
pciio_dmamap_done() | Make a DMA map inactive (may release hardware resources asslociated to the map). |
pciio_dmamap_free() | Release a DMA map object. |
pciio_dmatrans_addr() | Request immediate translation of the address of a contiguous memory buffer to a bus address. Returns NULL unless this system supports fixed DMA addressing |
Maps are allocated with pciio_dmamap_alloc(). Its use is covered under “Allocating PIO Maps”, because you typically will allocate the maps you need while attaching the device.
You obtain a map for a single, contiguous span of virtual memory by calling pciio_dmamap_addr(). It takes principle arguments of a map, a memory address, and a length. The value returned is a bus address that you can program into a bus master device. When the device accesses that address, it is accessing the specified memory location.
Once you have extracted an address using pciio_dmamap_addr(), the map is active. It remains active until you call either pciio_dmamap_done() or pciio_dmamap_free(). In the O2 workstation it costs nothing to keep a DMA map active. In other systems, an active map may tie up global hardware resources. It is is a good idea to call pciio_dmamap_done() when the I/O operation is complete.
In systems in which PCI space is hard-wired to specific memory addresses, pciio_dmamap_alloc() is a short function and pciio_dmamap_addr() is a trivial one. However, these systems also support a one-step translation function, pciio_dmatrans_addr(). This function takes a combination of the arguments of pciio_dmamap_alloc() and pciio_dmamap_addr(), and returns a translated address. In effect, it combines creating a map, using the map, and freeing the map, into a single step.
In some cases you are not sure whether a memory buffer is contiguous, or perhaps you are sure that it is not. In this case you need to create a list of memory addresses and lengths—one address and length for each segment of memory. Then you need to translate the segments into a list of bus addresses. The list of bus addresses can be programmed into the device one at a time or, if the device supports scatter/gather, you can program all of the list of addresses for transfer in sequence.
Support for these cases is proved by address-length lists, an abstract data type that is supported by a family of functions. The IRIX 6.4 contains a complete family of functions for address-length lists. IRIX 6.3 supports a subset necessary to use with DMA maps. The functions related to address-length lists are summarized in Table 15-5. See reference page alenlist(d4x) for an overview. For function syntax see alenlist_ops(d3x) and pciio_dma(d3).
Table 15-5. Functions for DMA Using Address-Length Lists
Function | Purpose and Operation |
|---|---|
alenlist_destroy() | Release an address-length list. |
alenlist_get() | Retrieve the next address and length pair from a list. |
buf_to_alenlist() | Create an address-length list to describe the buffer represented by a buf_t object. |
kvaddr_to_alenlist() | Create an address-length list to describe a buffer in kernel virtual memory. |
pciio_dmamap_list() | Convert an address-length list of memory addresses into an address-length list of corresponding bus addresses. |
pciio_dmatrans_list() | Request immediate conversion of an address-length list of memory addresses into an address-length list of corresponding bus addresses. Returns NULL unless this system supports fixed DMA mapping. |
The function buf_to_alenlist() is called in a pfxstrategy() entry point. It takes a buf_t and returns an address-length list that describes each segment of memory in the buffer that the buf_t describes (see “Structure buf_t” and “Entry Point strategy()”. The function kvaddr_to_alenlist() takes the address and length of any buffer in kernel virtual memory and returns an address-length list to describe that extent of memory.
When you are ready to perform DMA to a buffer, you create an address-length list to describe the buffer, and pass that through pciio_dmamap_list(). This function returns a new address-length list in which the memory addresses have been replaced by PCI bus addresses.
You step through the contents of the converted address-length list using alenlist_get(), which returns successive pairs of values—an address and a length—from the list. You program each pair of values into the bus master device.
In future releases of IRIX, the pfxdetach() entry point is called when the kernel decides to detach a PCI device. This can be caused by a hardware failure or by administrator action. In practice, it does not happen at all in IRIX 6.3 for O2. You may provide the entry point, but it is not called.
The functions for managing interrupt handlers are summarized in Table 15-6. See reference page pciio_intr(d3) for syntax.
Table 15-6. Functions for Managing PCI Interrupt Handlers
Function | Purpose and Operation |
|---|---|
pciio_intr_alloc() | Create an interrupt object that enables interrupts to flow from a specified device. |
pciio_intr_connect() | Associate an interrupt object with an interrupt handler function. |
pciio_intr_disconnect() | Remove the association between an interrupt object and a handler function. |
pciio_intr_free() | Release an interrupt object. |
The allocation of an interrupt handler is covered under “Registering an Interrupt Handler”. When detaching a device, call pciio_intr_disconnect() to break the association between the interrupt and the handler function.
When a loadable PCI driver is called at its pfxunload() entry point, indicating that the kernel would like to unload it, it must take great pains not to leave any dangling pointers (see “Entry Point unload()”). A driver should not unload when it has any registered interrupt handlers.
A driver does not have to unregister itself as a PCI driver before unloading. Nor does it have to detach any devices it has attached. However, if any devices are open or memory mapped, the driver should not unload.
If the kernel discovers a device and wants this driver to attach it, the kernel will reload the driver. If the driver has already attached one or more devices, the driver's information about the state of those devices is safely stored in each hardware vertex. When a process wants to open one of the devices, the driver will be reloaded automatically, and will be able to find the device information again.
Table 15-7 contains a summary of the PCI-related kernel functions, in alphabetical order. Click on a function name to bring up its reference page (when man pages are written!).
Table 15-7. PCI-Related Kernel Functions
Function | Purpose and Operation | Discussed |
|---|---|---|
alenlist_destroy() | Release an address-length list. | |
alenlist_get() | Retrieve the next address and length pair from a list. | |
buf_to_alenlist() | Create an address-length list to describe the buffer represented by a buf_t object. | |
hwgraph_device_add() | Add a device vertex for a logical device. | |
hwgraph_device_add_minor() | Associate a logical device vertex with a minor number. | |
kvaddr_to_alenlist() | Create an address-length list to describe a buffer in kernel virtual memory. | |
device_info_get() | Retrieve the address of device information from the hardware graph vertex. | |
device_info_set() | Store the address of device information in the hardware graph vertex. | |
pciio_config_get() | Fetch a defined register from configuration space using a base address returned by pciio_piomap_ addr(). | |
pciio_config_set() | Store a value into one of the defined fields of configuration space using an address returned by pciio_piomap_addr(). | |
pciio_dmamap_addr() | Get the bus virtual address corresponding to a memory address for a specified length. | |
pciio_dmamap_alloc() | Create a DMA map object, specifying the maximum extent of memory the map will have to cover. | |
pciio_dmamap_done() | Make a DMA map inactive (may release hardware resources asslociated to the map). | |
pciio_dmamap_free() | Release a DMA map object. | |
pciio_dmamap_list() | Convert an address-length list of memory addresses into an address-length list of corresponding bus addresses. | |
pciio_dmatrans_addr() | Request immediate translation of the address of a contiguous memory buffer to a bus address. Returns NULL unless this system supports fixed DMA addressing | |
pciio_dmatrans_list() | Request immediate conversion of an address-length list of memory addresses into an address-length list of corresponding bus addresses. Returns NULL unless this system supports fixed DMA mapping. | |
pciio_driver_register() | Notify the kernel that this driver is ready, and tell the vendor and device numbers it supports. | |
pciio_driver_unregister() | Notify the kernel this driver is not available (for example the driver is unloading). |
|
pciio_intr_alloc() | Create an interrupt object that enables interrupts to flow from a specified device. | |
pciio_intr_connect() | Associate an interrupt object with an interrupt handler function. | |
pciio_intr_disconnect() | Remove the association between an interrupt object and a handler function. |
|
pciio_intr_free() | Release an interrupt object. |
|
pciio_piomap_addr() | Get a kernel virtual address from a PIO map for a specific offset and length. | |
pciio_piomap_alloc() | Create a PIO map object, specifying the bus address space, base offset, and length it needs to cover. | |
pciio_piomap_done() | Make a PIO map inactive until it is next needed (may release hardware resources asslociated to the map). | |
pciio_piomap_free() | Release a PIO map object. | |
pciio_piotrans_addr() | Request immediate translation of a bus address to a kernel virtual address without use of a PIO map. Returns NULL unless this system supports fixed PIO addressing. |
The code in Example 15-7 implements a complete, working, PCI device driver for IRIX 6.3 for O2. This same source code is also available on the SGI Developer Toolbox CDROM, where you can also find the code for the user-level program that tests it.
Example 15-7 displays the descriptive file for /var/sysgen/master.d.
Example 15-8 displays the one-line VECTOR statement for /var/sysgen/system.
Example 15-9 displays the header file of device flags and information structure.
Example 15-10 displays the complete source code.
* * Barco Chameleon card * * Loadable driver: FLAG = fdN * Non-loadable: FLAG = c *FLAG PREFIX SOFT #DEV DEPENDENCIES cdN coco_ 73 - $$$ |
VECTOR: bustype=PCI module=coco |
/* =========================================
* Input/Output and Byte Swapping
* ========================================= */
#define BYTE_SWAP16(u) (ushort_t)(((u<<8)&0xff00)|((u>>8)&0x00ff))
#define BYTE_SWAP32(u) (uint_t)((u<<24)|((u<<8)&0xff0000)|((u>>8)&0xff00)|(u>>24))
/*
* byte swap Input/Output
*/
#if 0
#define Out8(addr, b) ( *(volatile uchar_t *)(addr) = (b) )
#define Out16(addr, s) ( *(volatile ushort_t *)(addr) = BYTE_SWAP16(s) )
#define Out32(addr, w) ( *(volatile uint_t *)(addr) = BYTE_SWAP32(w) )
#define Inp8(addr) ( *(volatile uchar_t *)(addr) )
#define Inp16(addr) BYTE_SWAP16( *(volatile ushort_t *)(addr) )
#define Inp32(addr) BYTE_SWAP32( *(volatile uint_t *)(addr) )
#endif
/*
* Input/Output with no byte swap
*/
#define Out8(addr, b) ( *(volatile uchar_t *)(addr) = (b) )
#define Out16(addr, w) ( *(volatile ushort_t *)(addr) = (w) )
#define Out32(addr, w) ( *(volatile uint_t *)(addr) = (w) );flushbus()
#define Inp8(addr) ( *(volatile uchar_t *)(addr) )
#define Inp16(addr) ( *(volatile ushort_t *)(addr) )
#define Inp32(addr) ( *(volatile uint_t *)(addr) )
/* ==========================
* Sleep/Wakeup/Lock
* ========================== */
#define COCO_LOCK splhi
#define COCO_UNLOCK(s) splx(s)
#define SleepEvent(x) psema(x, (PRIBIO|PCATCH) )
#define WakeEvent(x) vsema(x)
/* ==========================
* Misc. defaults
* ========================== */
#define DEVICE_ID 0x0001
#define VENDOR_ID 0x11a4
#define DRIVER_PREFIX "coco_"
#define MAJOR_NUMBER 73
#define AMCC_RAM_SIZE 64
#define CONFIG_RAM_SIZE 16
#define NORMAL_DMA_RAM_SIZE 16
#define COCO_CONFIG_HDR 68
#define END_OF_CHAIN 0x80000000
#define RW_TIMER 500 /* wait for read/write in clock ticks */
#define SIMRW_TIMER 500 /* wait for sim R/W in clock ticks */
#define CHAIN_FACTOR 10
#define MAPPED_SIZE 17*NBPP
#define COCO_CACHE_SIZE 32
/* =================================
* Configuration Register bits
* ================================= */
#define CONFIG_CCRES 0x00000001 /* reset when zero */
#define CONFIG_FRES 0x00000002 /* Input/Output Fifo reset when 0 */
#define CONFIG_FSDATI 0x00000004 /* Inp. Fifo serial config data */
#define CONFIG_FSDATO 0x00000008 /* Out. Fifo serial config data */
#define CONFIG_FSCLK 0x00000010 /* In/Out Fifo serial config clock */
#define CONFIG_LUTSEL 0x00000020 /* External LUT bank selection (=0) */
/* bits 6-9 is RAM address */
#define CONFIG_DMA_READ_ADDR 0x00000000 /* DMA Read Address */
#define CONFIG_DMA_WRITE_ADDR 0x00000040 /* DMA Write Address */
#define CONFIG_SC_READ_ADDR 0x00000080 /* Scatter-Gather Read Address */
#define CONFIG_SC_WRITE_ADDRR 0x00000000 /* Scatter-Gather Write Address */
/* ============================
* Mode Register
* ============================ */
#define COCO_MODE 0x01
#define COCO_SWAP 0x02
#define COCO_SLICE 0x04
#define COCO_DELAY 0x08
#define COCO_FLAG 0x20
#define COCO_WRENA 0x40
/* ==========================================
* Configuration Register
* ========================================== */
#define DMAREG_CCRES 0x00000001L /* Chameleon reset when zero */
#define DMAREG_HICRES 0x00000001L /* Chameleon reset when zero */
#define DMAREG_FRES 0x00000002L /* I/O Fifo reset when zero */
#define DMAREG_FSDATI 0x00000004L /* Input Fifo serial config. data */
#define DMAREG_FSDATO 0x00000008L /* Output Fifo serial config. data */
#define DMAREG_FSCLK 0x00000010L /* I/O serial config clock */
#define DMAREG_LED 0x00000020L /* external LUT bank selection (=0) */
/* bit 6-9: Ram address */
#define DMAREG_RAMRADR 0x00000000L /* DMA read address */
#define DMAREG_RAMWADR 0x00000040L /* DMA write address */
#define DMAREG_RAMPRRD 0x00000080L /* scatter-gather read address */
#define DMAREG_RAMPRWR 0x000000C0L /* scatter-gather write address */
#define DMAREG_RAMRCNT 0x00000100L /* read count copy */
#define DMAREG_RAMWCNT 0x00000140L /* write count copy */
#define DMAREG_FILL 0x00000400L /* enable automatic ram fill for DMA */
#define DMAREG_PTEN 0x00008000L /* Fifo Interface Enable */
#define DMAREG_INTREN 0x00010000L /* DMA Read Interrupt Enable */
#define DMAREG_INTWEN 0x00020000L /* DMA Write Interrupt Enable */
#define DMAREG_INTPREN 0x00040000L /* data-chained DMA read int. enable */
#define DMAREG_INTPWEN 0x00080000L /* data-chained DMA write int. enable */
/* bit 20-21: Device Selection */
#define DMAREG_RAM 0x00000000L /* RAM */
#define DMAREG_RCNT 0x00100000L /* DMA Read Count */
#define DMAREG_WCNT 0x00200000L /* DMA Write Count */
#define DMAREG_STAT 0x00300000L /* Status/Fifo control Register */
#define DMAREG_REN 0x10000000L /* DMA Read Enable */
#define DMAREG_WEN 0x20000000L /* DMA Write Enable */
#define DMAREG_PREN 0x40000000L /* data-chained DMA read enable */
#define DMAREG_PWEN 0x80000000L /* data-chained DMA write enable */
#define DMAREG_NVIFEN 0x00800000L /* Mailbox Interface Enable */
#define DMAREG_DMACVT 0x00400000L /* Unused */
/* Unknown !! */
#define DMAREG_HIDOIT 0x00000400L
#define DMAREG_HISCLK 0x00000800L
#define DMAREG_HISDI 0x00001000L
#define DMAREG_HISDO 0x00000080L
#define EOFPROG 0xF0000000L
/* ==============================
* AMCC Register Offsets
* ============================== */
#define AMCC_OP_REG_OMB1 0x00
#define AMCC_OP_REG_OMB2 0x04
#define AMCC_OP_REG_OMB3 0x08
#define AMCC_OP_REG_OMB4 0x0c
#define AMCC_OP_REG_IMB1 0x11
#define AMCC_OP_REG_IMB2 0x14
#define AMCC_OP_REG_IMB3 0x18
#define AMCC_OP_REG_IMB4 0x1c
#define AMCC_OP_REG_FIFO 0x20
#define AMCC_OP_REG_MWAR 0x24
#define AMCC_OP_REG_MWTC 0x28
#define AMCC_OP_REG_MRAR 0x2c
#define AMCC_OP_REG_MRTC 0x30
#define AMCC_OP_REG_MBEF 0x34
#define AMCC_OP_REG_INTCSR 0x38
#define AMCC_OP_REG_MCSR 0x3c
#define AMCC_OP_REG_MCSR_NVDATA (AMCC_OP_REG_MCSR + 2) /* Data in byte 2 */
#define AMCC_OP_REG_MCSR_NVCMD (AMCC_OP_REG_MCSR + 3) /* Command in byte 3 */
/* Amcc INTCSR interrupt bits */
#define AMCC_INTCSR_WEN 0x00004000
#define AMCC_INTCSR_REN 0x00008000
#define AMCC_INTCSR_INTMB4 0x00001f00
/* enable Output Mbox4, byte 3 only */
#define AMCC_INTCSR_MASK AMCC_INTCSR_INTMB4
#if 0 /* Write/Read Completion Interrupt enable only */
#define AMCC_INTCSR_MASK AMCC_INTCSR_WEN|AMCC_INTCSR_REN
/** "Write/Read Completion Interrupt" with Output Maibox4 */
#define AMCC_INTCSR_MASK AMCC_INTCSR_WEN|AMCC_INTCSR_REN|AMCC_INTCSR_INTMB4
#endif
#define AMCC_INTCSR_RCLR 0x00080000 /* Read Transfer Complete Clear */
#define AMCC_INTCSR_WCLR 0x00040000 /* Write Transfer Complete Clear */
#define AMCC_INTCSR_RST 0x00330000 /* Target/Master Abort and Out Mbox */
/* Amcc MCSR bits */
#define AMCC_REN 0x00004000 /* Read Enable */
#define AMCC_WEN 0x00000400 /* Write Enable */
#define AMCC_RFMAN 0x00002000L
#define AMCC_WFMAN 0x00000200L
#define AMCC_RFPRI 0x00001000L /* Read Priority over Write */
#define AMCC_WFPRI 0x00000100L /* Write Priority over Read */
#define AMCC_RST_FIFOS 0x06000000L /* Reset Fifos */
#define AMCC_RST_ADDON 0x01000000L /* Reset Add-on */
#define AMCC_MCSR_MASK AMCC_RST_FIFOS | AMCC_RST_ADDON
/* Outgoing Mailbox Register 4, byte 3 */
#define AMCC_MB_EOFDMAR 0x01000000 /* 1 = end of read DMA */
#define AMCC_MB_EOFDMAW 0x02000000 /* 1 = end of write DMA */
#define AMCC_MB_EOFPRDMAR 0x04000000 /* 1 = end of prog. read DMA */
#define AMCC_MB_EOFPRDMAW 0x08000000 /* 1 = end of prog. write DMA */
#define AMCC_MB_DIAGN 0x10000000 /* Diagnostic flag */
#define AMCC_MB_COCOMODE 0x20000000 /* Chameleon flag */
#define AMCC_MB_FIFORSTR 0x40000000 /* AMCC input Fifo Reset */
#define AMCC_MB_FIFORSTW 0x80000000 /* AMCC output Fifo Reset */
/* ==============================
* Device Information
* ==============================
*
* status: Shows whether the driver is attached/opened, etc.
* dmacfg: The boards Configuration default setting.
* dmabits: DMA operation bit setting (in addition to dmacfg)
* dmatype: Single or Chain DMA type
* dmastat: curretnt status of DMA (Idle, Wait, etc.)
* dmacmd: Board's DMA command (Transp or Convert)
* dmawait: Semaphore for wait/wakeup
* wp_addr: Residual phys. address for Write DMA.
* rp_addr: Residual phys. address for Read DMA.
* rp_size: Residual size in bytes for Read DMA.
* wp_size: Residual size in bytes for Write DMA.
* bp: Current buf_t pointer for Read/Write DMA
* chain_list: Address of current chain list buffer
* addrList: Current DMA scatter/gather structure (single read/write)
* r_addrList: Sim. Read/Write Read scatter/gather structure.
* w_addrList: Sim. Read/Write Write scatter/gather structure.
* page_no: Pages left to DMA (single read/write)
* r_page_no: Pages left for Read to DMA (Sim. read/write).
* w_page_no: Pages left for Write to DMA (Sim. read/write).
* cfg_adr: Board's Configuration Area Address
* amcc_adr: Amcc PCI address
* conf_adr: Board's Config Register address
* norm_adr: Board's Normal DMA Registers address
* start_time: Time DMA started
* intr_time: Time board Interrupt completion of DMA
* call_time: Time Call came into the driver
* ret_time: Time driver returns to the user
* vhdl: Vertex handle representing this board.
* dev_intr: Our Interrupt Handler structure
* dev_desc: Our Device Descriptor structure
* tid: Timer ID ..timer waiting for board to interrupt.
*/
typedef struct {
int status;
/* dma stuff */
uint_t dmacfg;
uint_t dmabits;
int dmatype;
int dmastat;
uint_t dmacmd;
sema_t dmawait;
int iostat;
int dmasize;
alenaddr_t wp_addr;
alenaddr_t rp_addr;
size_t rp_size;
size_t wp_size;
buf_t *bp;
caddr_t chain_list;
alenlist_t addrList;
alenlist_t r_addrList;
alenlist_t w_addrList;
int page_no;
int r_page_no;
int w_page_no;
/* mapped memory */
vhandl_t *vhandl;
caddr_t mappedkv;
int mappedkvlen;
/* addresses */
caddr_t cfg_adr;
caddr_t amcc_adr;
caddr_t conf_adr;
caddr_t norm_adr;
/* for Dma time measurement */
struct timeval start_time;
struct timeval intr_time;
struct timeval call_time;
struct timeval ret_time;
/* Irix interface structs */
vertex_hdl_t vhdl;
pciio_intr_t dev_intr;
device_desc_t dev_desc;
toid_t tid;
} card_t;
/* bits for status */
#define CARD_ATTACHED 0x01
#define CARD_OPEN 0x02
/* dmatype values */
#define DMA_PROG 0 /* chained - default value */
#define DMA_SINGLE 1
/* dmastat values */
#define DMA_IDLE 0
#define DMA_LUT_WAIT 1
#define DMA_READ_WAIT 2
#define DMA_WRITE_WAIT 3
#define DMA_RW_WAIT 4
/* iostat values */
#define IO_OK 0
#define IO_ERROR 1
#define IO_TIME 2
/* chained DMA block */
typedef struct {
paddr_t nextaddr;
paddr_t addr;
int size;
} coco_dmapage_t;
|
/******************************************************************************
***** C h a m e l e o n I r i x P c i D r i v e r *****
******************************************************************************
*/
#include <sys/types.h>
#include "sys/cmn_err.h"
#include "sys/sema.h"
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/conf.h>
#include <sys/pio.h>
#include "sys/systm.h"
#include <sys/time.h>
#include <sys/kmem.h>
#include <sys/ktime.h>
#include <sys/mload.h>
#include <sys/ddi.h>
#include <sys/cred.h>
#include <sys/user.h>
#include <sys/mace.h>
#include <sys/immu.h>
#include <sys/region.h>
#include <sys/alenlist.h>
#include <sys/IP32.h>
#include <sys/PCI/PCI_defs.h>
#include <sys/PCI/pciio.h>
#include "coco.h"
#include "coco_user.h"
char *coco_mversion = M_VERSION; /* loadable driver requirement */
int coco_devflag = 0; /* ddi/dki requirement */
/* ======================================
* Device Driver/PCI entry routines
* ====================================== */
int coco_unload(void);
int coco_open(dev_t *, int, int, cred_t *);
int coco_close(dev_t, int, int, cred_t *);
int coco_read( dev_t, uio_t *, cred_t *);
int coco_write( dev_t, uio_t *, cred_t *);
int coco_ioctl(dev_t, int, void *, int, cred_t *, int *);
int coco_map ( dev_t, vhandl_t *, off_t, int, int );
int coco_unmap ( dev_t, vhandl_t * );
int coco_init();
int coco_attach(vertex_hdl_t);
int coco_detach(vertex_hdl_t);
int coco_error(vertex_hdl_t, int );
void coco_intr( eframe_t *, intr_arg_t );
/* =================================
* Supporting internal routines
* ================================= */
static void cocoReset( card_t * );
static void cocoProgFlags(caddr_t,uint_t,ushort_t,ushort_t,ushort_t,ushort_t);
static void cocoSetMode ( card_t *, int, int, int, int );
static void cocoCommand ( card_t *, uint_t, uint_t );
static void cocoBufOut ( card_t *, uint_t *, int );
static void cocoBufIn ( card_t *, uint_t *, int );
static void cocoReadDmaRegs ( card_t *, uint_t * );
static void cocoWriteDmaRegs ( card_t *, uint_t * );
static void cocoSetAddr ( card_t *, uint_t );
static void cocoReadIntRam ( card_t *, uint_t *, int, int );
static void cocoWriteIntRam ( card_t *, uint_t *, int, int );
static void cocoReadExtRam ( card_t *, uint_t *, int );
static void cocoWriteExtRam ( card_t *, uint_t *, int );
static void cocoConvert ( card_t *, uint_t );
static void cocoConvertTest ( card_t *, coco_convert_t *);
static void cocoPrepAmcc ( card_t * );
static void cocoResetAmcc ( card_t *);
static void cocoStartProgDma ( card_t *, iopaddr_t, int, int );
static void cocoStartSingleDma ( card_t *, alenaddr_t, size_t, int );
static void cocoReport ( card_t *, char * );
static void cocoShowChain( char *, coco_dmapage_t * );
static void cocoTimeOut( card_t *);
static void cocoTimeOut2( card_t *);
static void cocoDumpAmcc ( card_t *);
static void cocoReportTime ( caddr_t, card_t *, int );
static void cocoShowAlenlist ( caddr_t, alenlist_t );
static void cocoUnlockUser ( caddr_t, int, int );
static void cocoDiffTime( struct timeval *,struct timeval *,struct timeval *);
static int cocoStrategy ( buf_t * );
static int cocoReadAmccFifo ( card_t * );
static int cocoFifoTest ( card_t *, int );
static int cocoPattern ( int, int );
static int cocoReadMode ( card_t * );
static int cocoReadAddr (card_t *);
static int cocoDmaRegsTest ( card_t * );
static int cocoIntRamTest ( card_t * );
static int cocoExtRamTest ( card_t * );
static int cocoDmaToLuts( card_t *, coco_buf_t *, int );
static int cocoReadWrite( card_t *, coco_rw_t * );
static int cocoStartRWDma ( card_t *, iopaddr_t, int, iopaddr_t, int );
static int cocoMakeChainRW ( card_t *, coco_dmapage_t **, coco_dmapage_t **);
static int cocoAlenlistSize ( alenlist_t );
static int cocoLockUser ( caddr_t, int, int );
static coco_dmapage_t * cocoMakeChain ( card_t *, alenlist_t, int );
static char *cocoIoctlStr(int);
/*
* temporary declaration of buf_to_alenlist
* - missing from pciio.h & alenlist.h
* - also not compatible with IRIX 6.4!
*/
extern alenlist_t buf_to_alenlist(buf_t *);
#define COCO_TEST 0x999
static void cocoDebug ( coco_rw_t * );
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~ ~~~~~~~~~~~~~
~~~~~~~~~~~ D r i v e r ' s E n t r y R o u t i n e s ~~~~~~~~~~~~~~
~~~~~~~~~~~ ~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/*************************************************************************
*** c o c o _ i n i t ***
*************************************************************************
*
* Name: coco_init
*
* Purpose: Called by kernel. Here we declare our PCI interface
* routines attach, detach and error and register our
* driver to take care of our card (card is identified by
* Vendor and Device IDs).
*
* Returns: None
*
*************************************************************************/
int
coco_init()
{
register int ret;
printf("coco_init()\n");
#ifdef _EARLY_PCI
/* Identify our attach, detach and error routines */
ret = pciio_add_attach(coco_attach, coco_detach,
(pciio_error_handler_f *)coco_error,
DRIVER_PREFIX, MAJOR_NUMBER);
if ( ret != 0 ) {
printf ("coco_init: could not add_attach\n");
return(0);
}
#endif
/* Register and identify the card */
ret = pciio_driver_register(VENDOR_ID, DEVICE_ID, DRIVER_PREFIX, 0);
if ( ret != 0 ) {
printf ( "coco_init: could not register\n");
return(0);
}
}
/*************************************************************************
*** c o c o _ a t t a c h ***
*************************************************************************
*
* Name: coco_attach
*
* Purpose: Called by the kernel. Our card is installed and hence
* we are called. Prepare everything necessary to handle
* the card. Note that when the card is found, the following
* is set in the Command field of Config space:
* - Bus master enabled.
* - Memory and IO space access enabled.
* - Cache line is set to 0x20 (32)
* - Latency Timer is set to 0x30 (48)
*
* For Chameleon, beside Configuration address space, we need 4
* Memory address spaces to be mapped:
* Base_Reg 0 = AMCC Registers and Fifos
* Base_Reg 3 = Normal DMA registers
* Base_Reg 4 = Configuration Register.
*
* Returns: 0 for Success, or errno
*
*************************************************************************/
int
coco_attach(vertex_hdl_t vhdl)
{
card_t *cp;
caddr_t cfg_adr, mem_ptr, amcc_adr, conf_adr, norm_adr;
int ret;
u_int32_t vendor_id, device_id, base_reg, cmd_reg;
device_desc_t dev_desc;
#ifdef DEBUG
printf ("coco_attach: ---- start --------\n");
#endif
/* =========================
* Configuration Space
* ========================= */
/* Get a pointer to the card's Configuration address space */
cfg_adr = (caddr_t)pciio_piotrans_addr ( vhdl, NULL, PCIIO_SPACE_CFG,
(iopaddr_t)0, COCO_CONFIG_HDR, 0);
if ( cfg_adr == (caddr_t)NULL ) {
cmn_err ( CE_WARN, "coco_attach: Cannot get to Config space");
return(EIO);
}
#ifdef DEBUG
printf ("Config. address is 0x%x\n", cfg_adr );
/*
* Get Vendor_Id, Device_Id and Base_Reg and print them
* Here we get only Base_Reg 0. Get others if your card uses more.
* Beside these general fields, get any vendor specific fields
* and check/print them for debugging purpose.
*/
vendor_id = pciio_config_get(cfg_adr, PCI_CFG_VENDOR_ID);
device_id = pciio_config_get(cfg_adr, PCI_CFG_DEVICE_ID);
cmd_reg = pciio_config_get(cfg_adr, PCI_CFG_COMMAND );
printf ("Coco Vendor_Id = 0x%x, Device_Id = 0x%x, Cmd = 0x%x\n",
vendor_id, device_id, cmd_reg );
#endif
/* ==========================
* Memory Address Space
* ========================== */
/* Get Amcc Register addresses */
amcc_adr = (caddr_t)pciio_piotrans_addr ( vhdl, NULL, PCIIO_SPACE_WIN(0),
(iopaddr_t)0, AMCC_RAM_SIZE, 0);
#ifdef DEBUG
printf ("Amcc address is 0x%x\n", amcc_adr );
#endif
if ( amcc_adr == (caddr_t)NULL ) {
cmn_err(CE_WARN, "coco_attach: Cannot get to AMCC address space");
return (EIO);
}
/* Get DMA Config Register address */
conf_adr = (caddr_t)pciio_piotrans_addr ( vhdl, NULL, PCIIO_SPACE_WIN(4),
(iopaddr_t)0, CONFIG_RAM_SIZE, 0);
#ifdef DEBUG
printf ("Config Register Address is 0x%x\n", conf_adr );
#endif
if ( conf_adr == (caddr_t)NULL ) {
cmn_err(CE_WARN, "coco_attach: Cannot get to Config Register");
return (EIO);
}
/* Get Normal DMA Register addresses */
norm_adr = (caddr_t)pciio_piotrans_addr ( vhdl, NULL, PCIIO_SPACE_WIN(3),
(iopaddr_t)0, NORMAL_DMA_RAM_SIZE,
0);
#ifdef DEBUG
printf ("Normal DMA address is 0x%x\n", norm_adr);
#endif
if ( norm_adr == (caddr_t)NULL ) {
cmn_err(CE_WARN, "coco_attach: Cannot get to Normal DMA address");
return (EIO);
}
/* allocate an internal structure for this card and save everything */
cp = (card_t *)kmem_zalloc ( sizeof(card_t), KM_NOSLEEP );
if ( cp == (card_t *)NULL ) {
cmn_err(CE_WARN, "coco_attach: Cannot allocate memory");
return(ENOMEM);
}
cp->vhdl = vhdl;
cp->cfg_adr = cfg_adr;
cp->amcc_adr = amcc_adr;
cp->conf_adr = conf_adr;
cp->norm_adr = norm_adr;
/* =====================================
* Interrupt Handler Registration
* ===================================== */
dev_desc = kmem_alloc(sizeof (*dev_desc), KM_SLEEP);
dev_desc->intr_swlevel = COCO_LOCK;
cp->dev_intr = pciio_intr_alloc ( vhdl, dev_desc, PCIIO_INTR_LINE_A, vhdl );
if (cp->dev_intr == (pciio_intr_t)NULL){
cmn_err(CE_WARN, "coco_attach: Can't pciio_intr_alloc");
kmem_free (dev_desc, sizeof(*dev_desc) );
kmem_free ( cp, sizeof(card_t) );
return(EIO);
}
ret = pciio_intr_connect ( cp->dev_intr, (intr_func_t)coco_intr,
(intr_arg_t)cp, 0 );
if ( ret == -1 ) {
cmn_err(CE_WARN, "coco_attach: Cannot register interrupt handler");
kmem_free (dev_desc, sizeof(*dev_desc) );
kmem_free ( cp, sizeof(card_t) );
return (EIO);
}
cp->dev_desc = dev_desc;
cp->status = CARD_ATTACHED;
/* allocate memory for mapping */
cp->mappedkv = kmem_alloc (MAPPED_SIZE,
KM_NOSLEEP | KM_PHYSCONTIG | KM_CACHEALIGN);
if ( cp->mappedkv == (caddr_t)NULL ) {
cmn_err (CE_NOTE, "coco_attach: Not enough memory for %d mapping",
MAPPED_SIZE);
cmn_err (CE_WARN, "coco_attach: No mapping is allowed");
}
else {
cp->mappedkvlen = MAPPED_SIZE;
cmn_err (CE_NOTE, "coco_attach: %d bytes available for mapping",
cp->mappedkvlen );
}
/* save our structure */
device_info_set ( vhdl, (arbitrary_info_t)cp );
cmn_err ( CE_NOTE, "coco_attach: driver is ready");
return(0);
}
/*************************************************************************
*** c o c o _ d e t a c h ***
*************************************************************************
*
* Name: coco_detach
*
* Purpose: Detaches a driver
*
* Returns: 0 Success or errno
*
*************************************************************************/
int
coco_detach(vertex_hdl_t vhdl)
{
register card_t *cp;
cmn_err(CE_NOTE,
"coco_detach: Unregister Interrupt handler and free up mem");
cp = (card_t *)device_info_get ( vhdl );
/* free up memory for mapping */
if ( cp->mappedkv )
kmem_free ( cp->mappedkv, cp->mappedkvlen );
/* Unregister the Interrupt Handler */
pciio_intr_disconnect(cp->dev_intr);
pciio_intr_free(cp->dev_intr);
kmem_free ( cp->dev_desc, sizeof(*cp->dev_desc) );
kmem_free ( cp, sizeof(card_t) );
return(0);
}
/*************************************************************************
*** c o c o _ i n t r ***
*************************************************************************
*
* Name: coco_intr
*
* Purpose: Our interrupt handler.
*
* Returns: None.
*
*************************************************************************/
void
coco_intr( eframe_t *ef, intr_arg_t arg )
{
register card_t *cp;
register buf_t *bp;
register caddr_t adr_cfg, adr_amcc, adr_norm;
register uint_t intcsr, xil_stat, mbox;
register uint_t dmastat, dmatype, dmacfg, dmabits;
register int rw, err;
size_t p_size;
alenaddr_t p_addr;
/* is it stray interrupt ? */
cp = (card_t *)arg;
microtime ( & cp->intr_time)
adr_amcc = cp->amcc_adr;
intcsr = Inp32(adr_amcc+AMCC_OP_REG_INTCSR);
if ( (intcsr & 0x00800000) == 0 ) {
#ifdef DEBUG
printf ("cocoIntr: Stray interrupt\n");
#endif
return;
}
#ifdef DEBUGTIME
cocoReportTime ( "cocoIntr", cp, 0 );
#endif
bp = cp->bp;
adr_cfg = cp->conf_adr;
adr_norm = cp->norm_adr;
dmacfg = cp->dmacfg;
dmabits = cp->dmabits;
dmastat = cp->dmastat;
dmatype = cp->dmatype;
#ifdef DEBUG
printf ("cocoIntr: started, tid = %d\n", cp->tid );
cocoDumpAmcc(cp);
#endif
/* cancel any outstanding timer */
if ( cp->tid > 0 ) {
untimeout(cp->tid);
cp->tid = 0;
}
/* ======================================
* Reseting Interrupt and Status
* ====================================== */
/* disable any Dma and read in Xilinx status */
Out32(adr_cfg, dmacfg | DMAREG_STAT );
xil_stat = Inp32(adr_norm);
mbox = Inp32(adr_amcc+AMCC_OP_REG_IMB4);
#ifdef DEBUG
printf ("coco_intr: amcc: 0x%x, xilinx: 0x%x, mbox4 = 0x%x\n",
intcsr, xil_stat, mbox );
#endif
/* Reset Amcc Interrupts and enable interrupts again */
Out32(adr_amcc+AMCC_OP_REG_INTCSR, AMCC_INTCSR_RST | AMCC_INTCSR_RCLR |
AMCC_INTCSR_WCLR );
Out32(adr_amcc+AMCC_OP_REG_INTCSR, AMCC_INTCSR_MASK);
/* ===============================================
* End of Dma Read or Write
* =============================================== */
if ( (mbox & AMCC_MB_EOFDMAR) || (mbox & AMCC_MB_EOFDMAW) ) {
dmabits &= ~(DMAREG_REN | DMAREG_INTREN);
dmabits &= ~(DMAREG_WEN | DMAREG_INTWEN );
#ifdef DEBUG
if ( mbox & AMCC_MB_EOFDMAR)
printf ("cocoIntr: End of DMA Read\n");
else printf ("cocoIntr: End of DMA Write\n");
#endif
}
/* ===============================================
* End of Chain Dma Read or Write
* =============================================== */
if ( (mbox & AMCC_MB_EOFPRDMAR) || (mbox & AMCC_MB_EOFPRDMAW) ) {
dmabits &= ~(DMAREG_PREN | DMAREG_INTPREN);
dmabits &= ~(DMAREG_PWEN | DMAREG_INTPWEN);
dmabits &= ~(DMAREG_WEN | DMAREG_REN);
#ifdef DEBUG
if ( mbox & AMCC_MB_EOFPRDMAR )
printf ("cocoIntr: End Prog DMA Read\n");
else printf ("cocoIntr: End Prog DMA Write\n");
#endif
}
cp->dmabits = dmabits;
cp->iostat = IO_OK;
switch ( cp->dmastat ) {
/* ==============================
* Dma to Lut
* ============================== */
case DMA_LUT_WAIT:
#ifdef DEBUG
printf ("cocoIntr: Waking up Dma_Lut_Wait\n");
#endif
cp->dmastat = DMA_IDLE;
WakeEvent(&cp->dmawait);
goto get_out;
/* ==================================
* Read/Write Dma
* ================================== */
case DMA_READ_WAIT:
case DMA_WRITE_WAIT:
/* what we do depends on which type of Dma is done */
switch ( cp->dmatype ) {
/* chained Dma is done. Simply wake the process up */
case DMA_PROG:
#ifdef DEBUG
printf ("cocoIntr: biodone() read/write\n");
#endif
kmem_free ( cp->chain_list,
cp->page_no * sizeof(coco_dmapage_t) );
alenlist_done(cp->addrList);
cp->addrList = 0;
cp->dmastat = DMA_IDLE;
bp->b_resid -= cp->dmasize;
biodone(cp->bp);
goto get_out;
/* single page Dma done. Dma the next page if any */
case DMA_SINGLE:
bp->b_resid -= cp->dmasize;
cp->page_no--;
if ( cp->page_no <= 0 ) { /* no more pages */
#ifdef DEBUG
printf ("cocoIntr: No more pages to Dma\n");
printf ("cocoIntr: biodone() read/write\n");
#endif
alenlist_done(cp->addrList);
cp->addrList = 0;
cp->dmastat = DMA_IDLE;
biodone(cp->bp);
goto get_out;
}
/* get next page to DMA */
#ifdef DEBUG
printf ("cocoIntr: Dma next page ..left = %d\n",
cp->page_no-1 );
#endif
if ( alenlist_get(cp->addrList, NULL, NBPP,
&p_addr, &p_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN,
"cocoIntr: Bad scatter-gather");
cp->iostat = IO_ERROR;
#ifdef DEBUG
printf ("cocoIntr: biodone() read/write\n");
#endif
alenlist_done(cp->addrList);
cp->addrList = 0;
cp->dmastat = DMA_IDLE;
bioerror (cp->bp, EIO);
biodone(cp->bp);
goto get_out;
}
if ( cp->dmastat == DMA_READ_WAIT )
rw = B_READ;
else rw = B_WRITE;
cocoStartSingleDma ( cp, p_addr, p_size, rw );
goto get_out;
} /*** switch ( cp->dmatype ) ***/
/* ==========================
* Simultaneus read/write
* ========================== */
case DMA_RW_WAIT:
/* what we do depends on which type of Dma is done */
switch ( cp->dmatype ) {
/* chained Dma is done. Both read/write is done */
case DMA_PROG:
#ifdef DEBUG
printf ("cocoIntr: Waking up Dma_RW_Wait\n");
#endif
cp->dmastat = DMA_IDLE;
WakeEvent(&cp->dmawait);
goto get_out;
/* single page Dma done. Dma the next page if any */
case DMA_SINGLE:
/* Keep transfering as long as there is data */
if ( cp->w_page_no > 0 ||
cp->r_page_no > 0 ||
cp->wp_size > 0 ||
cp->rp_size > 0 ) {
#ifdef DEBUG
printf ("cocoIntr: Dma next page, w_left = %d, r_left = %d\n",
cp->w_page_no, cp->r_page_no);
#endif
err = cocoStartRWDma(cp, 0, 0, 0, 0);
if ( err == 0 )
goto get_out;
#ifdef DEBUG
printf ("cocoIntr: error Dmaing next page\n");
#endif
cp->dmastat = DMA_IDLE;
cp->iostat = IO_ERROR;
WakeEvent(&cp->dmawait);
goto get_out;
}
#ifdef DEBUG
printf ("cocoIntr: Waking up Dma_RW_Wait\n");
#endif
cp->dmastat = DMA_IDLE;
WakeEvent(&cp->dmawait);
goto get_out;
} /*** switch ( cp->dmatype ) ***/
} /*** switch ( cp->dmastat ) ***/
get_out:
#ifdef DEBUG
cocoReport ( cp, "cocoIntr exit" );
#endif
return;
}
/*************************************************************************
*** c o c o _ u n l o a d ***
*************************************************************************
*
* Name: coco_unload
*
* Purpose: Unloads the driver.
*
* Returns: 0 Success or errno
*
*************************************************************************/
int
coco_unload(void)
{
printf ("coco_unload: Unloading the driver\n");
pciio_driver_unregister(DRIVER_PREFIX);
return(0);
}
/*************************************************************************
*** c o c o _ o p e n ***
*************************************************************************
*
* Name: coco_open
*
* Purpose: Opens the card. This sample driver simply verifies that
* the card's info structure can be retrieved and checks
* the card's Base_Register.
*
* Returns: 0 = Success, or errno.
*
*************************************************************************/
int
coco_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
register card_t *cp;
register vertex_hdl_t vhdl;
#ifdef DEBUG
printf ("coco_open: Opening the card\n");
#endif
/* Get the vertex handle and pointer to card's info */
vhdl = dev_to_vhdl ( *devp );
if (vhdl == NULL){
cmn_err(CE_WARN, "coco_open: dev_to_vhdl returns NULL");
return(EIO);
}
cp = (card_t *)device_info_get ( vhdl );
/* some error checking first */
if ( !(cp->status & CARD_ATTACHED) ) {
cmn_err (CE_WARN, "coco_open: Driver is not attached");
return (ENODEV);
}
if ( cp->status & CARD_OPEN) {
cmn_err (CE_WARN, "coco_open: Device is busy");
return (EBUSY);
}
cocoReset(cp); /* reset the board */
cp->status |= CARD_OPEN;
/* default values */
cp->dmatype = DMA_SINGLE;
cp->dmacmd = COCO_TRANSP;
return(0);
}
/*************************************************************************
*** c o c o _ c l o s e ***
*************************************************************************
*
* Name: coco_close
*
* Purpose: Closes the card. This sample driver's close statement
* prints out the addresses and Base_Register's value for
* verification.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
int
coco_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
register card_t *cp;
register vertex_hdl_t vhdl;
#ifdef DEBUG
printf ("coco_close: Closing the card\n");
#endif
/* get the vertex handle and pointer to card's info */
vhdl = dev_to_vhdl ( dev );
cp = (card_t *)device_info_get ( vhdl );
cp->status &= ~CARD_OPEN;
return(0);
}
/*************************************************************************
*** c o c o _ m a p ***
*************************************************************************
*
* Name: coco_map
*
* Purpose: Allocate a piece of continious memory and map it to user's
* address space.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
int
coco_map ( dev_t dev, vhandl_t *vh, off_t offset, int len, int prot )
{
register int ret;
register caddr_t kv;
register vertex_hdl_t vhdl;
register card_t *cp;
/* get the vertex handle and pointer to card's info */
vhdl = dev_to_vhdl ( dev );
cp = (card_t *)device_info_get ( vhdl );
if ( cp->mappedkv == (caddr_t)NULL ) {
cmn_err (CE_NOTE, "coco_map: Not memory for mapping");
return (ENOMEM);
}
if ( len > cp->mappedkvlen ) {
cmn_err (CE_NOTE,
"coco_map: Only %d bytes available for map, requested %d bytes",
cp->mappedkvlen, len );
return (ENOMEM);
}
ret = v_mapphys ( vh, cp->mappedkv, len );
if ( ret > 0 ) {
cmn_err (CE_WARN, "coco_map: Could not map, ret = %d", ret );
return (ret);
}
/* save for later */
cp->vhandl = vh;
dki_dcache_inval ( cp->mappedkv, cp->mappedkvlen );
printf ("coco_map: v_mapphys returned %d, mapped 0x%x for %d bytes\n",
ret, cp->mappedkv, len );
return (0);
}
/*************************************************************************
*** c o c o _ u n m a p ***
*************************************************************************
*
* Name: coco_unmap
*
* Purpose: Unmap the kernel buffer we allocated before.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
int
coco_unmap ( dev_t dev, vhandl_t *vh )
{
return (0);
}
/*************************************************************************
*** c o c o _ i o c t l ***
*************************************************************************
*
* Name: coco_ioctl
*
* Purpose: Handles user Ioctl command. These commands can be
* found in coco_user.h.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
int
coco_ioctl(dev_t dev, int cmd, void *arg, int mode, cred_t *cred,
int *rvalp )
{
register card_t *cp;
register vertex_hdl_t vhdl;
register uint_t *tmp_ibuf;
register int tot_bytes, err, i;
uint_t tmp_int;
coco_buf_t coco_buf;
coco_rw_t coco_rw;
coco_mode_t coco_mode;
coco_cmd_t coco_cmd;
coco_convert_t coco_convert;
uint_t dmaRegs[DMA_REGS];
#ifdef DEBUG
printf ("\ncoco_ioctl: ---> command = %s [0x%x]\n",
cocoIoctlStr(cmd), cmd );
#endif
/* get the vertex handle and pointer to card's info */
vhdl = dev_to_vhdl ( dev );
cp = (card_t *)device_info_get ( vhdl );
/* is device opened ? */
if (!( cp->status & CARD_OPEN) ) {
cmn_err (CE_NOTE, "coco_ioctl: Device is not opened");
return (EIO);
}
bzero ( &cp->start_time, sizeof(struct timeval) );
bzero ( &cp->intr_time, sizeof(struct timeval) );
bzero ( &cp->call_time, sizeof(struct timeval) );
bzero ( &cp->ret_time, sizeof(struct timeval) );
/* ===================
* Ioctl commands
* =================== */
switch ( cmd ) {
case COCO_TEST:
/* read in coco_rw_t struct..all we need is there */
if ( copyin(arg, (char *)&coco_rw, sizeof(coco_rw_t) ) )
return (EFAULT);
cocoDebug ( &coco_rw );
return(0);
/* ==============================
* Board Identification
* ============================== */
case COCO_ISPCI:
*rvalp = 1;
break;
/* ==============================
* Reset the board
* ============================== */
case COCO_RESET:
cocoReset(cp);
break;
/* ==============================
* Set DMA Command
* ============================== */
case COCO_SETCMD_TRANSP:
cp->dmacmd = COCO_TRANSP;
break;
case COCO_SETCMD_CONVERT:
cp->dmacmd = COCO_CONVERT;
break;
/* ===============================
* Issue a Command
* =============================== */
case COCO_COMMAND:
if ( copyin ( (char *)arg, &coco_cmd,
sizeof(coco_cmd_t) ) )
return (EFAULT);
cocoCommand ( cp, coco_cmd.cmd, coco_cmd.datav );
break;
/* ===================================
* Set DMA type (Prog or Single)
* =================================== */
case COCO_SET_SINGLE_DMA:
#ifdef DEBUG
printf ("Chameleon DMA set to Single Mode\n");
#endif
cp->dmatype = DMA_SINGLE;
break;
case COCO_SET_PROG_DMA:
#ifdef DEBUG
printf ("Chameleon DMA set to Prog Mode\n");
#endif
cp->dmatype = DMA_PROG;
break;
/* ===============================
* Set up Chameleon Mode
* =============================== */
case COCO_SET_MODE:
if ( copyin ( (char *)arg, &coco_mode,
sizeof(coco_mode_t) ) )
return (EFAULT);
cocoSetMode ( cp, coco_mode.mode, coco_mode.swap,
coco_mode.slice, coco_mode.flag );
break;
/* =================================
* Read Chameleon Mode
* ================================= */
case COCO_READ_MODE:
tmp_int = cocoReadMode (cp);
if ( copyout((char *)&tmp_int, arg, sizeof(uint_t) ) )
return (EFAULT);
break;
/* ===============================
* Raw write Buffer to Fifo *
* =============================== */
case COCO_RAW_WRITEB_FIFO:
if ( copyin( (char *)arg, &coco_buf, sizeof(coco_buf_t) ) )
return (EFAULT);
if ( coco_buf.buf_size == 0 )
return (EINVAL);
/*
* the data is in coco_buf.buf in user address
* space. Move it to kernel address space and dump it
* to the board.
*/
tot_bytes = coco_buf.buf_size * sizeof(uint_t);
tmp_ibuf = (uint_t *)kmem_alloc (tot_bytes, KM_NOSLEEP);
if ( tmp_ibuf == (uint_t *)NULL )
return (ENOMEM);
if ( copyin((caddr_t)coco_buf.buf, (caddr_t)tmp_ibuf,
tot_bytes) ) {
kmem_free (tmp_ibuf, tot_bytes);
return(EFAULT);
}
cocoBufOut (cp, tmp_ibuf, coco_buf.buf_size);
kmem_free (tmp_ibuf, tot_bytes );
break;
/* ===============================
* Raw Read Buffer from Fifo *
* =============================== */
case COCO_RAW_READB_FIFO:
if ( copyin( (char *)arg, &coco_buf, sizeof(coco_buf_t) ) )
return (EFAULT);
if ( coco_buf.buf_size == 0 )
return (EINVAL);
/*
* the buffer to be filled is coco_buf.buf and
* it can contain coco_buf.buf_size 32-bit values.
* Read that many into our own temp buffer and move them
* to user-address space.
*/
tot_bytes = coco_buf.buf_size * sizeof(uint_t);
tmp_ibuf = (uint_t *)kmem_alloc (tot_bytes, KM_NOSLEEP);
if ( tmp_ibuf == (uint_t *)NULL )
return (ENOMEM);
cocoBufIn(cp, tmp_ibuf, coco_buf.buf_size);
if ( copyout ( (caddr_t)tmp_ibuf, (caddr_t)coco_buf.buf,
tot_bytes)) {
kmem_free ( tmp_ibuf, tot_bytes );
return (EFAULT);
}
kmem_free ( tmp_ibuf, tot_bytes );
break;
/* =============================
* Read 32-bit from Fifo
* ============================= */
case COCO_RAW_READ_FIFO:
tmp_int = cocoReadAmccFifo(cp);
if ( copyout((char *)&tmp_int, arg, sizeof(uint_t) ) )
return(EFAULT);
break;
/* =============================
* Write 32-bit value to Fifo
* ============================= */
case COCO_RAW_WRITE_FIFO:
if ( copyin(arg, (char *)&tmp_int, sizeof(uint_t) ) )
return(EFAULT);
cocoCommand(cp, COCO_TRANSP, tmp_int );
break;
/* =============================
* Test Fifo data path
* ============================= */
case COCO_FIFO_TEST:
return (cocoFifoTest(cp, 1) );
/* ===============================
* Read DMA Registers
* =============================== */
case COCO_RAW_READ_DMA:
cocoReadDmaRegs(cp, &dmaRegs[0]);
if ( copyout((char *)&dmaRegs[0], arg,
sizeof(dmaRegs) ) )
return (EFAULT);
break;
/* ===============================
* Write DMA Registers
* =============================== */
case COCO_RAW_WRITE_DMA:
if ( copyin(arg, (char *)&dmaRegs[0],
sizeof(dmaRegs) ) )
return (EFAULT);
cocoWriteDmaRegs(cp, &dmaRegs[0] );
break;
/* =============================
* DMA Regsters Access Test
* ============================= */
case COCO_DMAREGS_TEST:
return ( cocoDmaRegsTest(cp) );
/* ===============================
* Read Address Register
* =============================== */
case COCO_READ_ADDR:
tmp_int = cocoReadAddr(cp);
if ( copyout((char *)&tmp_int, arg, sizeof(uint_t) ) )
return (EFAULT);
break;
/* ===============================
* Write Address Register
* =============================== */
case COCO_SET_ADDR:
if ( copyin(arg, (char *)&tmp_int, sizeof(uint_t) ) )
return(EFAULT);
cocoSetAddr(cp, tmp_int);
break;
/* ===============================
* Internal RAM Test
* =============================== */
case COCO_INTRAM_TEST:
return ( cocoIntRamTest(cp) );
/* ===============================
* External RAM Test
* =============================== */
case COCO_EXTRAM_TEST:
return ( cocoExtRamTest(cp) );
/* ==============================
* Read Internal LUTs
* ============================== */
case COCO_READ_RAMIL:
case COCO_READ_RAMIH:
case COCO_READ_RAMO:
if ( copyin( (char *)arg, (char *)&coco_buf,
sizeof(coco_buf_t) ) )
return (EFAULT);
if ( coco_buf.buf_size == 0)
return (EINVAL);
if ( coco_buf.buf_size > INT_RAM_SIZE )
return (EINVAL);
/* allocate a buffer to hold Ram's contents */
tot_bytes = coco_buf.buf_size * sizeof(uint_t);
tmp_ibuf = (uint_t *)kmem_alloc (tot_bytes, KM_NOSLEEP);
if ( tmp_ibuf == (uint_t *)NULL )
return(EFAULT);
/* read in Ram's contents into the buffer */
cocoReadIntRam(cp, tmp_ibuf, cmd, coco_buf.buf_size );
/* copy the kernel buffer to user's buffer */
if ( copyout((caddr_t)tmp_ibuf, (caddr_t)coco_buf.buf,
tot_bytes) ) {
kmem_free ( (caddr_t)tmp_ibuf, tot_bytes );
return (EFAULT);
}
kmem_free ( (caddr_t)tmp_ibuf, tot_bytes );
break;
/* ================================
* Write Internal LUTs
* ================================ */
case COCO_FILL_RAMIL:
case COCO_FILL_RAMIH:
case COCO_FILL_RAMO:
if ( copyin( (char *)arg, &coco_buf, sizeof(coco_buf_t) ) ) return (EFAULT);
if ( coco_buf.buf_size == 0)
return (EINVAL);
if ( coco_buf.buf_size > 2 * INT_RAM_SIZE )
return (EINVAL);
/* allocate a buffer to hold user's data */
tot_bytes = coco_buf.buf_size * sizeof(uint_t);
tmp_ibuf = (uint_t *)kmem_alloc (tot_bytes, KM_NOSLEEP);
if ( tmp_ibuf == (uint_t *)NULL )
return(EFAULT);
/* copy user's buffer to our own */
if ( copyin((caddr_t)coco_buf.buf, (caddr_t)tmp_ibuf,
tot_bytes ) ) {
kmem_free ( tmp_ibuf, tot_bytes );
return(EFAULT);
}
/* fill the Ram with data just moved over */
cocoWriteIntRam ( cp, tmp_ibuf, cmd,
coco_buf.buf_size );
kmem_free ( tmp_ibuf, tot_bytes );
break;
/* ==============================
* Read External LUT
* ============================== */
case COCO_READ_RAML:
if ( copyin( (char *)arg, &coco_buf, sizeof(coco_buf_t) ) ) return (EFAULT);
if ( coco_buf.buf_size == 0)
return (EINVAL);
if ( coco_buf.buf_size > EXT_RAM_SIZE )
return (EINVAL);
/* allocate a buffer to hold External Ram's contents */
tot_bytes = coco_buf.buf_size * sizeof(uint_t);
tmp_ibuf = (uint_t *)kmem_alloc (tot_bytes, KM_NOSLEEP);
if ( tmp_ibuf == (uint_t *)NULL )
return(EFAULT);
/* fill the buffer with Ext. Ram's contents */
cocoReadExtRam(cp, tmp_ibuf, coco_buf.buf_size);
/* copy the data to user's buffer */
if ( copyout((caddr_t)tmp_ibuf, (caddr_t)coco_buf.buf,
tot_bytes) ) {
kmem_free ( tmp_ibuf, tot_bytes );
return (EFAULT);
}
kmem_free ( tmp_ibuf, tot_bytes );
break;
/* ==============================
* Write External LUT
* ============================== */
case COCO_FILL_RAML:
if ( copyin( (char *)arg, &coco_buf, sizeof(coco_buf_t) ) ) return (EFAULT);
if ( coco_buf.buf_size == 0)
return (EINVAL);
if ( coco_buf.buf_size > 2 * EXT_RAM_SIZE )
return (EINVAL);
/* allocate a buffer to hold user's data */
tot_bytes = coco_buf.buf_size * sizeof(uint_t);
tmp_ibuf = (uint_t *)kmem_alloc (tot_bytes, KM_NOSLEEP);
if ( tmp_ibuf == (uint_t *)NULL )
return(EFAULT);
/* copy user's buffer to our own */
if ( copyin((caddr_t)coco_buf.buf, (caddr_t)tmp_ibuf,
tot_bytes ) ) {
kmem_free ( tmp_ibuf, tot_bytes );
return(EFAULT);
}
/* fill the Ram with data just moved over */
cocoWriteExtRam ( cp, tmp_ibuf, coco_buf.buf_size);
kmem_free ( tmp_ibuf, tot_bytes );
break;
/* ==========================================
* Chameleon Single pixel Conversion
* ========================================== */
case COCO_CONVERT_PIXLE:
/* read in the coco_convert struct from user's space */
if ( copyin(arg, (char *)&tmp_int, sizeof(uint_t) ) )
return (EFAULT);
cocoConvert ( cp, tmp_int );
break;
/* ============================================
* Chameleon Single pixel Conversion Test
* ============================================ */
case COCO_CONVERT_TEST:
/* read in the coco_convert struct from user's space */
if ( copyin(arg, (char *)&coco_convert,
sizeof(coco_convert_t) ) )
return (EFAULT);
cocoConvertTest ( cp, &coco_convert );
/* copy the structure back */
if ( copyout((char *)&coco_convert, arg,
sizeof(coco_convert_t) ) )
return(EFAULT);
break;
/* ================================================
* DMA fill of LUTs (Internals and External)
* ================================================ */
case COCO_BLOCK_FILL_RAMIL:
case COCO_BLOCK_FILL_RAMIH:
case COCO_BLOCK_FILL_RAMO:
case COCO_BLOCK_FILL_RAML:
/* read in coco_buf_t struct..all we need is there */
if ( copyin(arg, (char *)&coco_buf, sizeof(coco_buf_t) ) ) return (EFAULT);
/* empty buffer ? */
if ( coco_buf.buf_size == 0 )
return (EINVAL);
/* DMA the data and report back the result */
microtime ( &cp->call_time );
err = cocoDmaToLuts(cp, &coco_buf, cmd );
microtime ( &cp->ret_time );
#ifdef DEBUGTIME
cocoReportTime ( "cocoDmaToLut", cp, 1 );
#endif
return (err);
/* ================================================
* Simultaneous Read/Write DMA
* ================================================ */
case COCO_RW_BUF:
/* read in coco_rw_t struct..all we need is there */
if ( copyin(arg, (char *)&coco_rw, sizeof(coco_rw_t) ) )
return (EFAULT);
if ( coco_rw.buf_size <= 0 ) {
cmn_err (CE_NOTE,
"cocoIoctl: Bad Sim.read/write buff size of %d\n",
coco_rw.buf_size );
return(EINVAL);
}
microtime ( &cp->call_time );
err = cocoReadWrite(cp, &coco_rw );
microtime ( &cp->ret_time );
#ifdef DEBUGTIME
cocoReportTime ( "cocoReadWrite", cp, 1 );
#endif
if ( err != 0 )
cmn_err (CE_NOTE,
"cocoIoctl: cocoReadWrite reported error %d\n", err );
return (err);
} /*** end switch **/
return(0);
}
/*************************************************************************
*** c o c o _ r e a d ***
*************************************************************************
*
* Name: coco_read
*
* Purpose: Read entry routine. DMAs data from the board to the
* user's address space.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
int
coco_read( dev_t dev, uio_t *uiop, cred_t *crp)
{
register card_t *cp;
register vertex_hdl_t vhdl;
register int ret;
/* get to the board's info structure */
vhdl = dev_to_vhdl ( dev );
cp = (card_t *)device_info_get ( vhdl );
cp->bp = (buf_t *)NULL;
cp->addrList = 0;
cp->dmastat = 0;
cp->dmabits = 0;
bzero ( &cp->start_time, sizeof(struct timeval) );
bzero ( &cp->intr_time, sizeof(struct timeval) );
bzero ( &cp->call_time, sizeof(struct timeval) );
bzero ( &cp->ret_time, sizeof(struct timeval) );
#ifdef DEBUG
printf ("coco_read: Reading %d bytes\n", uiop->uio_resid );
#endif
/* do the transfer */
microtime ( &cp->call_time );
ret = uiophysio ( cocoStrategy, NULL, dev, B_READ, uiop );
microtime ( &cp->ret_time );
if ( cp->addrList ) {
alenlist_done(cp->addrList);
cp->addrList = 0;
}
#ifdef DEBUG
printf ("coco_read: uiophysio retunred %d\n", ret );
#endif
#ifdef DEBUGTIME
cocoReportTime ( "cocoRead", cp, 1);
#endif
return ( ret );
}
/*************************************************************************
*** c o c o _ w r i t e ***
*************************************************************************
*
* Name: coco_write
*
* Purpose: Write entry routine. DMAs data from user's address space
* to the board.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
int
coco_write( dev_t dev, uio_t *uiop, cred_t *crp)
{
register card_t *cp;
register vertex_hdl_t vhdl;
register int ret;
/* get to the board's info structure */
vhdl = dev_to_vhdl ( dev );
cp = (card_t *)device_info_get ( vhdl );
cp->bp = (buf_t *)NULL;
cp->dmastat = 0;
cp->dmabits = 0;
cp->addrList = 0;
bzero ( &cp->start_time, sizeof(struct timeval) );
bzero ( &cp->intr_time, sizeof(struct timeval) );
bzero ( &cp->call_time, sizeof(struct timeval) );
bzero ( &cp->ret_time, sizeof(struct timeval) );
#ifdef DEBUG
printf ("coco_write: Writing %d bytes\n", uiop->uio_resid );
#endif
/* do the transfer */
microtime ( &cp->call_time );
ret = uiophysio ( cocoStrategy, NULL, dev, B_WRITE, uiop );
microtime ( &cp->ret_time );
if ( cp->addrList ) {
alenlist_done(cp->addrList);
cp->addrList = 0;
}
#ifdef DEBUG
printf ("coco_write: uiophysio retunred %d\n", ret );
#endif
#ifdef DEBUGTIME
cocoReportTime ( "cocoWrite", cp, 1);
#endif
return ( ret );
}
/*************************************************************************
*** c o c o _ e r r o r ***
*************************************************************************
*
* Name: coco_error
*
* Purpose: Traps PCI bus error.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
int
coco_error(vertex_hdl_t vhdl, int error)
{
printf("coco_error %d\n", error );
return(0);
}
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~ S u p p o r t i n g R o u t i n e s ~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/*************************************************************************
*** c o c o S t r a t e g y ***
*************************************************************************
*
* Name: cocoStrategy
*
* Purpose: Strategy routine. It actually handles read/write
* and starts the DMA. It is called by uiophysio() kernel
* routine.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
static int
cocoStrategy ( buf_t *bp )
{
register card_t *cp;
register vertex_hdl_t vhdl;
register coco_dmapage_t *dmaPg;
register int s, i, ret, rw, tot_bytes;
register uint_t fill_bits;
alenlist_t addrList2;
size_t p_size;
alenaddr_t p_addr;
iopaddr_t p_dmaPg;
if ( !BP_ISMAPPED(bp) ) {
cmn_err (CE_NOTE, "cocoStrategy: Unmapped buf_t used\n");
bioerror ( bp, EIO);
biodone (bp);
return(EIO);
}
/* get to the board's info structure */
vhdl = dev_to_vhdl ( bp->b_edev );
cp = (card_t *)device_info_get ( vhdl );
/* clear error and save bp */
bioerror(bp, 0);
bp->b_resid = bp->b_bcount;
cp->bp = bp;
if ( bp->b_flags & B_READ )
rw = B_READ; /* board -> mem */
else rw = B_WRITE; /* mem -> board */
/* create scatter-gather list */
addrList2 = buf_to_alenlist ( bp );
cp->addrList = pciio_dmatrans_list ( cp->vhdl, cp->dev_desc,
addrList2,
PCIIO_DMAMAP_BIGEND);
/* PCIIO_DMAMAP_LITTLEEND); */
alenlist_done (addrList2);
if ( cp->addrList == (alenaddr_t)NULL ) {
cmn_err (CE_NOTE, "cocoStrategy: Cannot create alenlist");
bioerror ( bp, EIO);
biodone (bp);
return(EIO);
}
#if 0
cp->page_no = alenlist_size ( cp->addrList );
#endif
cp->page_no = cocoAlenlistSize ( cp->addrList );
#ifdef DEBUG
printf ("cocoStrategy: %s %d bytes [%d pages] in %s Dma\n",
rw == B_READ ? "Reading":"Writing",
bp->b_bcount, cp->page_no,
cp->dmatype == DMA_PROG ? "Chain":"Single" );
#endif
/* set the command to be executed during Dma */
cp->dmabits = cp->dmacmd;
cocoPrepAmcc ( cp );
/* =======================
* Chained DMA
* ======================= */
if ( cp->dmatype == DMA_PROG ) {
dmaPg = cocoMakeChain ( cp, cp->addrList, cp->page_no );
if ( dmaPg == (coco_dmapage_t *)NULL ) {
printf ("cocoStrategy: Error creating chain list\n");
alenlist_done (cp->addrList);
bioerror (bp, EIO);
biodone (bp);
return(EIO);
}
tot_bytes = cp->page_no * sizeof(coco_dmapage_t);
p_dmaPg = pciio_dmatrans_addr ( cp->vhdl, cp->dev_desc,
kvtophys(dmaPg), tot_bytes,
PCIIO_DMAMAP_BIGEND );
/* write back cache for chain list */
dki_dcache_wbinval ( dmaPg, tot_bytes );
s = COCO_LOCK();
/* start the Chained Dma */
cocoStartProgDma ( cp, p_dmaPg, bp->b_bcount, rw );
if ( rw == B_READ )
cp->dmastat = DMA_READ_WAIT;
else cp->dmastat = DMA_WRITE_WAIT;
cp->chain_list = (caddr_t)dmaPg;
#ifdef DEBUG
printf ("cocoStrategy: Waiting for chain interrupt\n");
#endif
/* so we dont wait forever for an interrupt */
cp->tid = itimeout(cocoTimeOut, cp, RW_TIMER,
pltimeout, 0, 0, 0);
COCO_UNLOCK(s);
return(0);
}
/* ===========================
* Single page DMA
* =========================== */
/* get a page to DMA */
if ( alenlist_get(cp->addrList, NULL, NBPP,
&p_addr, &p_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN, "cocoDma: Not enough Memory");
alenlist_done(cp->addrList);
bioerror ( bp, EIO);
biodone(bp);
return(EIO);
}
/* single dma, memory -> board */
s = COCO_LOCK();
cocoStartSingleDma ( cp, p_addr, p_size, rw );
if ( rw == B_READ )
cp->dmastat = DMA_READ_WAIT;
else cp->dmastat = DMA_WRITE_WAIT;
cp->chain_list = NULL;
#ifdef DEBUG
printf ("cocoStrategy: Waiting for Single interrupt\n");
#endif
/* so we dont wait forever for an interrupt */
cp->tid = itimeout(cocoTimeOut, cp, RW_TIMER,
pltimeout, 0, 0, 0);
#ifdef DEBUG
printf ("cocoStrategy: Waiting for Single interrupt, tid = %d\n",
cp->tid );
#endif
COCO_UNLOCK(s);
return(0);
}
/*************************************************************************
*** c o c o R e a d W r i t e ***
*************************************************************************
*
* Name: cocoReadWrite
*
* Purpose: Starts simultaneous Read and Write DMA to user's space.
*
* Returns: 0 for success, or errno
*
*************************************************************************/
static int
cocoReadWrite ( card_t *cp, coco_rw_t *rw )
{
register caddr_t dum;
register caddr_t w_kvaddr, r_kvaddr;
register uint_t *cache_line;
register int r_page_no, w_page_no, s, err, i, cache_bytes;
register int tot_r_cache, tot_w_cache, tot_bytes;
register int coco_adjust_chain, tot_wrong;
coco_dmapage_t *w_dmaPg, *r_dmaPg;
alenlist_t addrList2;
iopaddr_t pr_dmaPg, pw_dmaPg;
cocoResetAmcc(cp);
cocoPrepAmcc(cp);
tot_bytes = rw->buf_size * sizeof(uint_t);
coco_adjust_chain = rw->adjust_chain;
/* =========================================================
* Scatter-Gather list for Write buffer (mem -> board)
* ========================================================= */
/* lock user's pages in memory for DMA */
if ( cocoLockUser ( (caddr_t)rw->w_buf, tot_bytes, B_WRITE) != 0 ) {
cmn_err (CE_WARN, "cocoReadWrite: Cannot lock user's pages");
return (EFAULT);
}
/*
* write back and invalidate the data for mem->board
* adjust the address for current data cache size
*/
#ifdef R10000
cache_line = (uint_t *)rw->w_buf;
cache_line = (uint_t *)( ((uint_t)cache_line / COCO_CACHE_SIZE) *
COCO_CACHE_SIZE );
cache_bytes = tot_bytes + ( (uint_t)rw->w_buf - (uint_t)cache_line);
dki_dcache_wbinval ( (caddr_t)cache_line, cache_bytes );
#else
dki_dcache_wbinval ( rw->w_buf, tot_bytes );
if ( cp->mappedkv )
dki_dcache_wbinval (cp->mappedkv, cp->mappedkvlen );
#endif
/* create scatter-gather list of user's buffer */
addrList2 = uvaddr_to_alenlist( (alenlist_t)NULL, (caddr_t)rw->w_buf,
(size_t)tot_bytes );
/* did we make it ? */
if ( addrList2 == (alenlist_t)NULL ) {
cmn_err (CE_WARN, "cocoreadWrite: cannot create Wrt alenlist");
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes, B_WRITE );
return (EIO);
}
cp->w_addrList = pciio_dmatrans_list ( cp->vhdl, cp->dev_desc,
addrList2, PCIIO_DMAMAP_BIGEND);
#if 0
w_page_no = alenlist_size ( cp->w_addrList );
#endif
w_page_no = cocoAlenlistSize ( cp->w_addrList );
cp->w_page_no = w_page_no;
alenlist_done ( addrList2 );
#ifdef DEBUG4
printf ("cocoReadWrite: Write %d bytes [%d pages]\n",
tot_bytes, w_page_no );
#endif
/* =========================================================
* Scatter-Gather list for Read buffer ( board -> mem )
* ========================================================= */
/* lock user's pages in memory for DMA */
if ( cocoLockUser ( (caddr_t)rw->r_buf, tot_bytes, B_READ) != 0 ) {
cmn_err (CE_WARN, "cocoReadWrite: Cannot lock user's pages");
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes, B_WRITE );
return (EFAULT);
}
/*
* Invalidate the cache for board->mem
* adjust the address for current data cache line size
*/
#ifdef R10000
cache_line = (uint_t *)rw->r_buf;
cache_line = (uint_t *)( ((uint_t)cache_line / COCO_CACHE_SIZE) *
COCO_CACHE_SIZE );
cache_bytes = tot_bytes + ( (uint_t)rw->r_buf - (uint_t)cache_line);
dki_dcache_inval ( (caddr_t)cache_line, cache_bytes);
#else
dki_dcache_inval ( rw->r_buf, ((tot_bytes/NBPP)+1)*NBPP );
#endif
/* create scatter-gather list */
addrList2 = uvaddr_to_alenlist( (alenlist_t)NULL, (caddr_t)rw->r_buf,
(size_t)tot_bytes);
/* did we make it ? */
if ( addrList2 == (alenlist_t)NULL ) {
cmn_err (CE_WARN, "cocoreadWrite: cannot create Rd alenlist");
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes, B_WRITE );
cocoUnlockUser ( (caddr_t)rw->r_buf, tot_bytes, B_READ);
return (EIO);
}
cp->r_addrList = pciio_dmatrans_list ( cp->vhdl, cp->dev_desc,
addrList2, PCIIO_DMAMAP_BIGEND);
#if 0
r_page_no = alenlist_size ( cp->r_addrList );
#endif
r_page_no = cocoAlenlistSize ( cp->r_addrList );
cp->r_page_no = r_page_no;
alenlist_done ( addrList2 );
#ifdef DEBUG4
printf ("cocoReadWrite: Read %d bytes [%d pages]\n",
tot_bytes, r_page_no );
#endif
/* assume Single Dma type */
w_dmaPg = (coco_dmapage_t *)NULL;
r_dmaPg = (coco_dmapage_t *)NULL;
/* ======================================================
* Chain DMA - Prepare chain list for read and write
* ====================================================== */
if ( cp->dmatype == DMA_PROG ) {
/* need to adjust read/write byte counts */
if ( (coco_adjust_chain == 1) &&
(rw->r_buf != rw->w_buf) ) {
err = cocoMakeChainRW ( cp, &w_dmaPg, &r_dmaPg );
if ( err ) {
cmn_err (CE_WARN,
"cocoReadWrite: Could not make Chain List, err = %d",
err );
cocoUnlockUser ( (caddr_t)rw->r_buf, tot_bytes,
B_READ);
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes,
B_WRITE);
alenlist_done(cp->r_addrList);
alenlist_done(cp->w_addrList);
return (err);
}
}
/* no need to adjust read/write byte counts */
else {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Prepare Chain List for Write
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
#ifdef DEBUG
printf (
"cocoReadWrite: Preparing Chain for Write, %d pages\n",
w_page_no );
#endif
w_dmaPg = cocoMakeChain ( cp, cp->w_addrList, w_page_no );
if ( w_dmaPg == (coco_dmapage_t *)NULL ) {
printf ("cocoReadWrite: Error creating chain list\n");
cocoUnlockUser ( (caddr_t)rw->r_buf, tot_bytes,
B_READ);
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes,
B_WRITE);
alenlist_done(cp->r_addrList);
alenlist_done(cp->w_addrList);
return(EIO);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Prepare Chain List for Read
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
#ifdef DEBUG
printf (
"cocoReadWrite: Preparing Chain for Read, %d pages\n",
r_page_no );
#endif
r_dmaPg = cocoMakeChain ( cp, cp->r_addrList, r_page_no );
if ( r_dmaPg == (coco_dmapage_t *)NULL ) {
printf ("cocoReadWrite: Error creating chain list\n");
cocoUnlockUser ( (caddr_t)rw->r_buf, tot_bytes,
B_READ);
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes,
B_WRITE);
alenlist_done(cp->r_addrList);
alenlist_done(cp->w_addrList);
kmem_free ( w_dmaPg,
w_page_no * sizeof(coco_dmapage_t));
return(EIO);
}
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Map to PCI and cache management
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* map Write chain list */
if ( coco_adjust_chain == 1 )
tot_w_cache = w_page_no * CHAIN_FACTOR *
sizeof(coco_dmapage_t);
else tot_w_cache = w_page_no * sizeof(coco_dmapage_t);
pw_dmaPg = pciio_dmatrans_addr ( cp->vhdl, cp->dev_desc,
kvtophys(w_dmaPg),
tot_w_cache,
PCIIO_DMAMAP_BIGEND );
#ifdef DEBUG
cocoShowChain( "Write Chain list", w_dmaPg );
#endif
/* write back the cache for this chain list */
dki_dcache_wbinval ( w_dmaPg, tot_w_cache );
/* map Read chain list */
if ( coco_adjust_chain == 1 )
tot_r_cache = r_page_no * CHAIN_FACTOR *
sizeof(coco_dmapage_t);
else tot_r_cache = r_page_no * sizeof(coco_dmapage_t);
pr_dmaPg = pciio_dmatrans_addr ( cp->vhdl, cp->dev_desc,
kvtophys(r_dmaPg),
tot_r_cache,
PCIIO_DMAMAP_BIGEND );
#ifdef DEBUG
cocoShowChain( "Read Chain List", r_dmaPg );
#endif
/* write back the cache for this chain list */
dki_dcache_wbinval ( r_dmaPg, tot_r_cache );
}
/* initialize our DMA event semaphore */
initnsema ( &cp->dmawait, 0, "coco" );
#ifdef DEBUG
printf ("cocoReadWrite: Read/Write %d bytes\n", tot_bytes );
#endif
cp->iostat = IO_OK;
cp->dmabits = cp->dmacmd;
cp->wp_addr = (alenaddr_t)0;
cp->rp_addr = (alenaddr_t)0;
cp->wp_size = 0;
cp->rp_addr = 0;
s = COCO_LOCK();
err = cocoStartRWDma( cp, pw_dmaPg, tot_bytes, pr_dmaPg, tot_bytes);
if ( err == 0 ) {
err = SleepEvent(&cp->dmawait);
if ( err ) {
err = EINTR;
cmn_err (CE_NOTE, "cocoReadWrite: Interrupted");
}
else {
if ( cp->iostat == IO_OK ) {
#ifdef DEBUG
printf ("cocoReadWrite: woken up\n");
#endif
}
if ( cp->iostat == IO_TIME ) {
cmn_err (CE_NOTE, "cocoReadWrite: Timed out");
err = ETIME;
}
if ( cp->iostat == IO_ERROR ) {
cmn_err (CE_NOTE, "cocoReadWrite: IO Error");
err = EIO;
}
}
}
else {
cmn_err (CE_WARN,
"cocoReadWrite: Could not start Sim read/write");
}
/* we are done */
cp->dmastat = DMA_IDLE;
cp->dmabits = 0;
/*
* Invalidate the cache for board->mem
* adjust the address for current data cache line size
*/
#ifdef R10000
cache_line = (uint_t *)rw->r_buf;
cache_line = (uint_t *)( ((uint_t)cache_line / COCO_CACHE_SIZE) *
COCO_CACHE_SIZE );
cache_bytes = tot_bytes + ( (uint_t)rw->r_buf - (uint_t)cache_line);
dki_dcache_inval ( (caddr_t)cache_line, cache_bytes);
#else
dki_dcache_inval ( rw->r_buf, ((tot_bytes/NBPP)+1)*NBPP );
#endif
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes, B_WRITE );
cocoUnlockUser ( (caddr_t)rw->r_buf, tot_bytes, B_READ);
alenlist_done(cp->r_addrList);
alenlist_done(cp->w_addrList);
if ( r_dmaPg ) kmem_free ( r_dmaPg, tot_r_cache );
if ( w_dmaPg ) kmem_free ( w_dmaPg, tot_w_cache);
COCO_UNLOCK(s);
return(err);
}
/*************************************************************************
*** c o c o S t a r t R W D m a ***
*************************************************************************
*
* Name: cocoStartRWDma
*
* Purpose: Program the boards for Simultaneous Read/Write DMA.
* The read and write channel's scatter-gather have been
* prepared before and are in cp->r_addrList and cp->w_addrList
* For Chained DMA, the chain list for read and write channels
* are prepared before and passed to us as parameters.
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
static int
cocoStartRWDma ( card_t *cp, iopaddr_t pw_dmaPg, int tot_write,
iopaddr_t pr_dmaPg, int tot_read )
{
register caddr_t adr_cfg, adr_norm, adr_amcc;
register int i, err, tot_words, tot_bytes;
register uint_t dmabits, dmacfg, dmacmd;
size_t rp_size, wp_size;
alenaddr_t rp_addr, wp_addr;
adr_cfg = cp->conf_adr;
adr_norm = cp->norm_adr;
adr_amcc = cp->amcc_adr;
dmacfg = cp->dmacfg;
dmabits = cp->dmabits;
dmacmd = cp->dmacmd;
Out32(adr_cfg, dmacfg ); /* clear eof markers */
/* =======================
* Chained DMA
* ======================= */
if ( cp->dmatype == DMA_PROG ) {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Programming the board for Read/Write Chain Dma
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* prepare Amcc counters */
Out32(adr_amcc+AMCC_OP_REG_MRTC, tot_write );
Out32(adr_amcc+AMCC_OP_REG_MWTC, tot_read );
/* Program the Write channel Address (board -> mem ) */
dmabits |= DMAREG_PREN | DMAREG_PWEN;
dmacfg |= dmabits;
Out32(adr_cfg, dmacfg);
/* Write channel address (b->m) */
Out32(adr_cfg, dmacfg | DMAREG_RAM | DMAREG_RAMPRWR );
Out32(adr_norm, pr_dmaPg);
/* Read channel address (m->b) */
Out32(adr_cfg, dmacfg | DMAREG_RAM | DMAREG_RAMPRRD );
Out32(adr_norm, pw_dmaPg);
/* Start Read/Write DMA - only Write(b->m) is enabled */
dmabits |= DMAREG_INTPWEN | DMAREG_WEN | DMAREG_REN | dmacmd;
cp->dmasize = tot_write;
cp->dmabits = dmabits;
cp->iostat = IO_OK;
cp->dmastat = DMA_RW_WAIT;
#ifdef DEBUG
printf (
"cocoStartRWDma: read chain = 0x%x for %d, write chain = 0x%x for %d\n",
pr_dmaPg, tot_read, pw_dmaPg, tot_write );
cocoReport (cp, "cocoStartRWDma: Chain Dma started");
#endif
/* start the DMA */
pciio_flush_buffers ( cp->vhdl );
microtime ( &cp->start_time );
Out32(adr_cfg, dmacfg | dmabits );
/* so we dont wait forever for an interrupt */
cp->tid = itimeout(cocoTimeOut2, cp, SIMRW_TIMER,
pltimeout, 0, 0, 0);
return(0);
}
/* ===========================
* Single page DMA
* =========================== */
/* get next page for Read channel if nothing left from past */
if ( cp->wp_addr == (alenaddr_t)0 ) {
if ( cp->w_page_no <= 0 ) {
#ifdef DEBUG
printf (
"cocoStartRWDma: Premature Write, w_page_no = %d, r_page_no = %d, wp_addr = 0x%x, rp_addr = 0x%x\n",
cp->w_page_no, cp->r_page_no, cp->wp_addr, cp->rp_addr );
#endif
cmn_err (CE_WARN, "cocoStartRWDma: Premature end of Write");
return(EIO);
}
if ( alenlist_get(cp->w_addrList, NULL, NBPP,
&wp_addr, &wp_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN, "cocoStartRWDma: bad scater-gather");
return(EIO);
}
cp->w_page_no--;
}
/* some bytes left from past */
else {
wp_addr = cp->wp_addr;
wp_size = cp->wp_size;
#ifdef DEBUG
printf (
"cocoStartRWDma: %d old bytes at 0x%x for read chan\n",
wp_size, wp_addr );
#endif
}
/* get next page for Write channel if nothing left from past */
if ( cp->rp_addr == (alenaddr_t)0 ) {
if ( cp->r_page_no <= 0 ) {
#ifdef DEBUG
printf (
"cocoStartRWDma: Premature Read, r_page_no = %d, w_page_no = %d, wp_addr = 0x%x, rp_addr = 0x%x\n",
cp->r_page_no, cp->w_page_no, cp->wp_addr, cp->rp_addr );
#endif
cmn_err (CE_WARN, "cocoStartRWDma: Premature end of Read");
return(EIO);
}
if ( alenlist_get(cp->r_addrList, NULL, NBPP,
&rp_addr, &rp_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN, "cocoReadWrite: bad scater-gather");
return(EIO);
}
cp->r_page_no--;
}
/* some bytes left from past */
else {
rp_addr = cp->rp_addr;
rp_size = cp->rp_size;
#ifdef DEBUG
printf (
"cocoStartRWDma: %d old bytes at 0x%x for Write chan\n",
rp_size, rp_addr );
#endif
}
/* adjust - we shoud write as much as we can read */
cp->wp_addr = (alenaddr_t)0;
cp->rp_addr = (alenaddr_t)0;
cp->wp_size = 0;
cp->rp_size = 0;
#ifdef DEBUG
printf (
"cocoStartRWDma: Original sizes, rp_size = %d at 0x%x, wp_size = %d at 0x%x\n",
rp_size, rp_addr, wp_size, wp_addr );
#endif
/* Write more than read ? */
if ( wp_size == rp_size )
tot_bytes = wp_size;
if ( wp_size > rp_size ) {
tot_bytes = rp_size;
cp->wp_addr = (alenaddr_t)((int)wp_addr + tot_bytes);
cp->wp_size = wp_size - tot_bytes;
}
/* read more than write ? */
if ( rp_size > wp_size ) {
tot_bytes = wp_size;
cp->rp_addr = (alenaddr_t)((int)rp_addr + tot_bytes);
cp->rp_size = rp_size - tot_bytes;
}
tot_words = (int)tot_bytes/sizeof(uint_t);
#ifdef DEBUG
printf (
"cocoStartRWDma: Single Dma %d [%d words], rp_addr = 0x%x, wp_addr = 0x%x\n",
tot_bytes, tot_words, rp_addr, wp_addr );
#endif
tot_words--; /* counts down to 0xffff in hardware */
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Program the board for Read and Write
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* prepare Amcc counters */
/* Out32(adr_amcc+AMCC_OP_REG_MRTC, tot_bytes ); */
Out32(adr_amcc+AMCC_OP_REG_MRTC, tot_bytes);
Out32(adr_amcc+AMCC_OP_REG_MWTC, tot_bytes );
/* program for Write channel ( board -> mem ) */
Out32(adr_cfg, dmacfg | DMAREG_WCNT);
Out32(adr_norm, (uint_t)tot_words);
Out32(adr_cfg, dmacfg | DMAREG_RAM | DMAREG_RAMWADR); /* b->m */
Out32(adr_norm, (uint_t)rp_addr);
/* program for Read channel ( mem -> board ) */
Out32(adr_cfg, dmacfg | DMAREG_RCNT);
Out32(adr_norm, (uint_t)tot_words);
Out32(adr_cfg, dmacfg | DMAREG_RAM | DMAREG_RAMRADR); /* b->m */
Out32(adr_norm, (uint_t)wp_addr);
/* start read/write Dma - only write channel (b->m) is needed */
dmabits |= DMAREG_INTWEN | DMAREG_REN | DMAREG_WEN | dmacmd;
cp->dmasize = wp_size;
cp->dmabits = dmabits;
cp->dmastat = DMA_RW_WAIT;
cp->iostat = IO_OK;
/* start the DMA */
pciio_flush_buffers ( cp->vhdl );
microtime ( &cp->start_time );
Out32(adr_cfg, dmacfg | dmabits | dmacmd );
/* so we dont wait forever for an interrupt */
cp->tid = itimeout(cocoTimeOut2, cp, SIMRW_TIMER,
pltimeout, 0, 0, 0);
return(0);
}
/*************************************************************************
*** c o c o R e s e t ***
*************************************************************************
*
* Name: cocoReset
*
* Purpose: This routine initializes the board by resetting the add-on
* logic, AMCC fifos, Chameleon chip and FIFOs.
* After reset, FIFO flags are programmed, default DMA controller
* config. register is written and Chameleon chip is enabled
*
* Returns: None
*
*************************************************************************/
static void
cocoReset( card_t *cp )
{
uint_t stat;
register caddr_t adr_amcc;
register caddr_t adr_cfg;
register uint_t tmp;
adr_amcc = cp->amcc_adr;
adr_cfg = cp->conf_adr;
/* disable interrupts */
Out32(adr_amcc+AMCC_OP_REG_INTCSR, AMCC_INTCSR_RST | AMCC_INTCSR_RCLR |
AMCC_INTCSR_WCLR );
/* reset AMCC fifos and Xilinx */
Out32(adr_amcc+AMCC_OP_REG_MCSR, AMCC_RST_ADDON );
Out32(adr_amcc+AMCC_OP_REG_MCSR, AMCC_RST_FIFOS );
/* reset Chameleon and FIFOs and bring FIFOs to programming flags */
Out32(adr_cfg, DMAREG_CCRES | DMAREG_FRES | DMAREG_FSCLK );
Out32(adr_cfg, DMAREG_FSCLK);
Out32(adr_cfg, DMAREG_FSCLK);
/* program FIFO flags */
cocoProgFlags ( adr_cfg, DMAREG_FRES,100,450,100,450);
/* bring FIFOs in functional mode and Chameleon out of reset */
Out32(adr_cfg, DMAREG_FRES | DMAREG_FSCLK);
Out32(adr_cfg, DMAREG_CCRES | DMAREG_FSCLK);
Out32(adr_cfg, DMAREG_CCRES | DMAREG_FSCLK | DMAREG_PTEN);
/* set default config. reg */
cp->dmacfg = DMAREG_NVIFEN | DMAREG_PTEN | DMAREG_CCRES | DMAREG_FSCLK;
/* initialize Chameleon */
cocoSetMode(cp, 0,0,0,1);
/* enable Amcc Interrupt */
Out32(adr_amcc+AMCC_OP_REG_INTCSR, AMCC_INTCSR_MASK );
}
/*************************************************************************
*** c o c o P r o g F l a g s ***
*************************************************************************
*
* Name: cocoProgFlags
*
* Purpose: Program offsets of ASIC fifo flags by writing 18 bit
* serial registers of FIFO containg 9 bit AF and 9 bit AE
* flag offset
*
* Returns: None
*
*************************************************************************/
static void
cocoProgFlags ( caddr_t adr_cfg, uint_t d, ushort_t iae, ushort_t iaf,
ushort_t oae, ushort_t oaf )
{
ushort_t andfl;
ushort_t shft;
ushort_t bit0;
ushort_t bit1;
uint_t bit;
andfl=0x01;
shft=0;
#ifdef DEBUG
printf ("cocoProgFlags: iae = 0x%x, oae = 0x%x, oaf = 0x%x\n",
iae, oae, oaf );
#endif
do {
bit0 = (iaf & andfl);
bit1 = (oaf & andfl);
bit = d | 0x00000010;
if (bit0 == 0)
bit |= 0x00000004;
if (bit1 == 0)
bit |= 0x00000008;
Out32(adr_cfg, bit);
bit &= 0xFFFFFFEF;
Out32(adr_cfg, bit);
andfl = andfl << 1;
shft++;
}
while ( shft <= 8 );
andfl = 0x01;
shft = 0;
do {
bit0 = (iae & andfl);
bit1 = (oae & andfl);
bit = d | 0x00000010;
if (bit0==0)
bit |= 0x00000004;
if (bit1==0)
bit |= 0x00000008;
Out32(adr_cfg, bit);
bit = bit & 0xFFFFFFEF;
Out32(adr_cfg, bit);
andfl = andfl << 1;
shft++;
}
while (shft<=8);
}
/*************************************************************************
*** c o c o S e t M o d e ***
*************************************************************************
*
* Name: cocoSetMode
*
* Purpose: Set Chameleon Mode Register
*
* Returns: None
*
*************************************************************************/
static void
cocoSetMode ( card_t *cp, int mode, int swap, int slice, int flag )
{
uint_t cmd;
cmd = COCO_WRENA | COCO_DELAY;
if (mode)
cmd |= COCO_MODE;
if (swap)
cmd |= COCO_SWAP;
if (slice)
cmd |= COCO_SLICE;
if (flag)
cmd |= COCO_FLAG;
#ifdef DEBUG
printf ("cocoSetMode: setting mode to 0x%x\n", cmd );
#endif
cocoCommand (cp, COCO_SETMODE, cmd);
}
/*************************************************************************
*** c o c o C o m m a n d ***
*************************************************************************
*
* Name: cocoCommand
*
* Purpose: Sends a command to Chameleon. Writes the command to
* the controller and writes the data to Fifo.
*
* Returns: None
*
*************************************************************************/
static void
cocoCommand ( card_t *cp, uint_t cmd, uint_t data )
{
register uint_t stat;
#if 0
stat = Inp32(cp->amcc_adr+AMCC_OP_REG_MCSR);
while ( stat & 0x1 ) {
printf ("Fifo full..waiting\n");
delay(1);
stat = Inp32(cp->amcc_adr+AMCC_OP_REG_MCSR);
}
#endif
/* set Chameleon connad in Config. Register */
Out32(cp->conf_adr, cmd | cp->dmacfg );
/* writes the data for the command to AMCC Fifo */
Out32(cp->amcc_adr + AMCC_OP_REG_FIFO, data);
}
/*************************************************************************
*** c o c o B u f O u t ***
*************************************************************************
*
* Name: cocoBufOut
*
* Purpose: Dumps a buffer containig 32-bit values to Chameleon Fifo.
* Data is given to Chameleon in Transparent mode.
*
* Returns: None
*
*************************************************************************/
static void
cocoBufOut ( card_t *cp, uint_t *buf, int size )
{
register int i, j;
register uint_t cmd, stat;
#ifdef DEBUG
printf ("cocoBufOut: outputing %d words to Fifo\n", size );
#endif
/* set to Transparent Mode */
cmd = cp->dmacfg | COCO_TRANSP;
Out32(cp->conf_adr, cmd );
/* Dump the data */
for ( i = 0; i < size; i++) {
j = 0;
stat = Inp32(cp->amcc_adr+AMCC_OP_REG_MCSR);
if ( stat & 0x03 ) { /* fifo has room */
Out32(cp->amcc_adr+AMCC_OP_REG_FIFO, buf[i] );
continue;
}
printf (
"cocoBufOut: Fifo is full, stat = 0x%x, discarding %d bytes\n",
stat, size - i );
break;
}
}
/*************************************************************************
*** c o c o B u f I n ***
*************************************************************************
*
* Name: cocoBufIn
*
* Purpose: Reads Fifo entries into a buf.
*
* Returns: None
*
*************************************************************************/
static void
cocoBufIn ( card_t *cp, uint_t *buf, int size )
{
register int i;
for ( i = 0; i < size; i++ ) {
buf[i] = cocoReadAmccFifo(cp);
}
}
/*************************************************************************
*** c o c o R e a d A m c c F i f o ***
*************************************************************************
*
* Name: cocoReadAmccFifo
*
* Purpose: Reads a 32-bit value word from AMCC Internal Fifo
* Returns zero if Fifo is empty
*
* Returns: Fifo value or zero if fifo is empty
*
*************************************************************************/
static int
cocoReadAmccFifo ( card_t *cp )
{
register uint_t stat;
register int i;
for ( i = 0; i < 3; i++ )
stat = Inp32(cp->amcc_adr+AMCC_OP_REG_MCSR);
/* if ( stat != 0x000000e6 ) { */
if ( stat != 0x1026 ) {
stat = Inp32(cp->amcc_adr + AMCC_OP_REG_FIFO );
return(stat);
}
return(0);
}
/*************************************************************************
*** c o c o F i f o T e s t ***
*************************************************************************
*
* Name: cocoFifoTest
*
* Purpose: Fills Fifo with a pattern, read it back one by one
* and compare. Reports back the results.
*
* Returns: 0 = Success, 1 = Failed.
*
*************************************************************************/
static int
cocoFifoTest ( card_t *cp, int pat )
{
register int i, j, err;
uint_t res, expect;
#ifdef DEBUG
printf ("cocoFifoTest: testing Fifo, pattern: %d\n", pat );
#endif
err = 0; /* assume success */
/* test for 50 times .. */
for( j = 0; j < 50; j++) {
/* fill Fifo with pattern */
for(i=0;i<750;i++)
cocoCommand (cp, COCO_TRANSP, cocoPattern(pat,i) );
/* read it back and compare */
for( i = 0;i < 750; i++) {
res = cocoReadAmccFifo( cp ); /* what we read */
expect = cocoPattern( pat, i); /* what we expect */
if (res != expect) {
printf(
"cocoFifoTest: Fifo entry %d, expected 0x%x, read 0x%x (round %d)\n",
i, expect, res, j);
err = 1;
}
}
}
return(err);
}
/*************************************************************************
*** c o c o P a t t e r n ***
*************************************************************************
*
* Name: cocoPattern
*
* Purpose: Pattern generator for testing purpose.
*
* Returns: The generated pattern
*
*************************************************************************/
static int
cocoPattern ( int pat, int cnt )
{
register int res;
/* for now, we always return pattern 1 */
pat=1;
switch (pat) {
case 1:
res = cnt;
break;
case 2:
res = ~cnt;
break;
case 3:
res = 0xFFFFFFFFL;
break;
case 4:
res = 0x00000000L;
break;
case 5:
if (cnt % 2 != 1)
res = 0xaaaaaaaaL;
else res = 0x55555555L;
break;
case 6:
cnt = (cnt & 0x01FL);
res = (1 << cnt);
break;
case 7:
cnt = (cnt & 0x01FL);
res = (1 << cnt);
res = ~res;
break;
case 8:
if ( cnt % 2 == 1)
res = 0x0f0f0f0fL;
else res = 0xf0f0f0f0L;
break;
case 9:
cnt = (cnt & 0xFF);
res = cnt + ( cnt << 8) + (cnt << 16) + (cnt << 24);
break;
case 10:
cnt = (cnt & 0xFFFF);
res = cnt + ( cnt << 16);
break;
}
return(res);
}
/*************************************************************************
*** c o c o R e a d M o d e ***
*************************************************************************
*
* Name: cocoReadMode
*
* Purpose: Reads Chameleon Mode register
*
* Returns: The generated pattern
*
*************************************************************************/
static int
cocoReadMode ( card_t *cp )
{
register uint_t mode;
/* set Chameleon command in Config. Register */
cocoCommand ( cp, COCO_READMODE, 0x0 );
mode = cocoReadAmccFifo(cp);
#ifdef DEBUG
printf ("cocoReadMode: Mode = 0x%x\n", mode );
#endif
return (mode);
}
/*************************************************************************
*** c o c o R e a d D m a R e g s ***
*************************************************************************
*
* Name: cocoReadDmaRegs
*
* Purpose: Reads Xilinx DMA Registers
*
* Returns: None.
*
*************************************************************************/
static void
cocoReadDmaRegs ( card_t *cp, uint_t *dmaRegs )
{
register int i, k;
/* read DMA registers into dmaRegs table */
i = 0;
for ( k = 13; k <= 16; k++) {
Out32(cp->conf_adr, cp->dmacfg | (k << 6) );
dmaRegs[i++] = Inp32(cp->norm_adr);
}
}
/*************************************************************************
*** c o c o W r i t e D m a R e g s ***
*************************************************************************
*
* Name: cocoWriteDmaRegs
*
* Purpose: Write values in dmaRegs[] to Xilinx DMA Registers
*
* Returns: None.
*
*************************************************************************/
static void
cocoWriteDmaRegs ( card_t *cp, uint_t *dmaRegs )
{
register int i, k;
/* read DMA registers into dmaRegs table */
i = 0;
for ( k = 13; k <= 16; k++) {
Out32(cp->conf_adr, cp->dmacfg | (k << 6) );
Out32(cp->norm_adr, dmaRegs[i++] );
}
}
/*************************************************************************
*** c o c o R e a d A d d r ***
*************************************************************************
*
* Name: cocoReadAddr
*
* Purpose: Reads Address Register
*
* Returns: Address Register value
*
*************************************************************************/
static int
cocoReadAddr (card_t *cp)
{
register uint_t addr_reg;
cocoCommand ( cp, COCO_READADDR, 0x0 );
addr_reg = cocoReadAmccFifo(cp);
#ifdef DEBUG
printf ("cocoreadAddr: Address Reg = 0x%x\n", addr_reg );
#endif
return(addr_reg);
}
/*************************************************************************
*** c o c o S e t A d d r ***
*************************************************************************
*
* Name: cocoSetAddr
*
* Purpose: Set Address Register to given value
*
* Returns: None
*
*************************************************************************/
static void
cocoSetAddr ( card_t *cp, uint_t val )
{
cocoCommand ( cp, COCO_SETADDR, val );
}
/*************************************************************************
*** c o c o D m a R e g s T e s t ***
*************************************************************************
*
* Name: cocoDmaRegsTest
*
* Purpose: Tests access to Xilinx DMA registers.
* Write and read to DMA registers, compare values and
* report back the results.
*
* Returns: Test result ( 0 = Success, 1 = Failed)
*
*************************************************************************/
static int
cocoDmaRegsTest ( card_t *cp )
{
register int i, j, k, err, res, expct, dmacfg, pat;
register caddr_t adr_cfg, adr_norm;
cmn_err (CE_NOTE, "testing Chameleon DMA registers access");
adr_cfg = cp->conf_adr;
adr_norm = cp->norm_adr;
dmacfg = cp->dmacfg;
err = 0;
pat = 1;
/* try for 500 times */
for ( i = 0; i < 500; i++ ) {
/* write to DMA Regsiers */
for ( k = 13; k <= 16; k++ ) {
Out32(adr_cfg, dmacfg | (k << 6) );
Out32(adr_norm, cocoPattern(pat, i+k) );
}
/* read back registers and compare */
for ( k = 13; k <= 16; k++ ) {
Out32(adr_cfg, dmacfg | (k << 6) );
res = Inp32(adr_norm);
expct = cocoPattern(pat, i+k);
/* not equal ..report it ! */
if ( res != expct ) {
cmn_err ( CE_NOTE,
"Dma reg %d, expected 0x%x, read 0x%x, round %x",
k, expct, res, i );
err = 1;
}
}
}
cmn_err (CE_NOTE, "Chameleon DMA registers access test %s",
(err == 0 ? "Succeeded": "Failed") );
return(err);
}
/*************************************************************************
*** c o c o I n t R a m T e s t ***
*************************************************************************
*
* Name: cocoIntRamTest
*
* Purpose: Test CHameleon Internal LUTs.
* Fills Internal LUT with a pattern and reads them back
* for comparison and reports the results.
*
* Returns: Test result ( 0 = Success, 1 = Failed)
*
*************************************************************************/
static int
cocoIntRamTest ( card_t *cp )
{
register int i, err, pat, num;
register uint_t res;
cmn_err (CE_NOTE, "Testing Chameleon Internal Ram access\n");
num = 1;
err = 0;
/* ====================
* Fill LUTs
* ==================== */
for ( i = 0; i < INT_RAM_SIZE; i++ ) {
pat = cocoPattern(num, i);
/* set address of internal LUT location */
cocoCommand (cp, COCO_SETADDR, i);
/* fill Internal LUTs with pattern */
cocoCommand (cp, COCO_FILLRAMIL, pat );
cocoCommand (cp, COCO_FILLRAMIH, pat );
cocoCommand (cp, COCO_FILLRAMO, pat );
}
/* =============================
* Read LUTs and compare
* ============================= */
for ( i = 0; i < INT_RAM_SIZE; i++ ) {
pat = cocoPattern(num, i) & 0x03ffff;
/*
* set address of internal RAMIL location
* read value from that location and compare
*/
cocoCommand (cp, COCO_SETADDR, i);
cocoCommand ( cp, COCO_READRAMIL, 0x0 );
res = cocoReadAmccFifo (cp);
/* is what we read ok ? */
if ( (res & 0x03ffff) != pat ) {
cmn_err (CE_NOTE,
"Chameleon RAMIL at 0x%x, expected 0x%x, read 0x%x\n",
i, pat, res );
err = 1;
}
/*
* set address of internal RAMIH location
* read value from that location and compare
*/
cocoCommand (cp, COCO_SETADDR, i);
cocoCommand ( cp, COCO_READRAMIH, 0x0 );
res = cocoReadAmccFifo (cp);
/* is what we read ok ? */
if ( (res & 0x03ffff) != pat ) {
cmn_err (CE_NOTE,
"Chameleon RAMIH at 0x%x, expected 0x%x, read 0x%x\n",
i, pat, res );
err = 1;
}
/*
* set address of internal RAMO location
* read value from that location and compare
*/
cocoCommand (cp, COCO_SETADDR, i);
cocoCommand ( cp, COCO_READRAMO, 0x0 );
res = cocoReadAmccFifo (cp);
/* is what we read ok ? */
if ( (res & 0x03ffff) != pat ) {
cmn_err (CE_NOTE,
"Chameleon RAMO at 0x%x, expected 0x%x, read 0x%x\n",
i, pat, res );
err = 1;
}
}
cmn_err (CE_NOTE, "Chameleon Internal RAM test %s\n",
( err == 0 ? "Succeeded":"Failed") );
return(err);
}
/*************************************************************************
*** c o c o E x t R a m T e s t ***
*************************************************************************
*
* Name: cocoExtRamTest
*
* Purpose: Test CHameleon External LUTs.
* Fills External LUT with a pattern and reads them back
* for comparison and reports the results.
*
* Returns: Test result ( 0 = Success, 1 = Failed)
*
*************************************************************************/
static int
cocoExtRamTest ( card_t *cp )
{
register int i, err, pat, num;
register uint_t res;
cmn_err (CE_NOTE, "Testing Chameleon External Ram access\n");
num = 1;
err = 0;
/* ====================
* Fill RAML
* ==================== */
for ( i = 0; i < EXT_RAM_SIZE; i++ ) {
/* set address of internal LUT location */
cocoCommand (cp, COCO_SETADDR, i);
/* fill External LUTs with pattern */
cocoCommand (cp, COCO_FILLRAML, cocoPattern(num, i) );
}
/* ===================================
* Read External LUT and compare
* =================================== */
for ( i = 0; i < EXT_RAM_SIZE; i++ ) {
pat = cocoPattern(num, i);
/*
* set address of internal RAMIL location
* read value from that location and compare
*/
cocoCommand (cp, COCO_SETADDR, i);
cocoCommand ( cp, COCO_READRAML, 0x0 );
res = cocoReadAmccFifo (cp);
/* is what we read ok ? */
if ( res != pat ) {
cmn_err (CE_NOTE,
"Chameleon RAML at 0x%x, expected 0x%x, read 0x%x\n",
i, pat, res );
err = 1;
}
}
cmn_err (CE_NOTE, "Chameleon External RAM test %s\n",
( err == 0 ? "Succeeded":"Failed") );
return(err);
}
/*************************************************************************
*** c o c o R e a d I n t R a m ***
*************************************************************************
*
* Name: cocoReadIntRam
*
* Purpose: Read Chameleon's Internal LUTs into given buffer.
*
* Returns: None.
*
*************************************************************************/
static void
cocoReadIntRam ( card_t *cp, uint_t *buf, int lut, int size )
{
register uint_t cmd;
register int i;
/* which LUT we should read ? */
switch ( lut ) {
case COCO_READ_RAMIL: cmd = COCO_READRAMIL; break;
case COCO_READ_RAMIH: cmd = COCO_READRAMIH; break;
case COCO_READ_RAMO: cmd = COCO_READRAMO; break;
}
/* fill buffer with contents of Internal Ram */
for ( i = 0; i < size; i++ ) {
cocoCommand (cp, COCO_SETADDR, i);
cocoCommand (cp, cmd, 0x0 );
buf[i] = cocoReadAmccFifo (cp);
}
}
/*************************************************************************
*** c o c o W r i t e I n t R a m ***
*************************************************************************
*
* Name: cocoWriteIntRam
*
* Purpose: Write Chameleon's Internal LUTs into given buffer.
*
* Returns: None.
*
*************************************************************************/
static void
cocoWriteIntRam ( card_t *cp, uint_t *buf, int lut, int size )
{
register uint_t cmd;
register int i;
/* which LUT we should read ? */
switch ( lut ) {
case COCO_FILL_RAMIL: cmd = COCO_FILLRAMIL; break;
case COCO_FILL_RAMIH: cmd = COCO_FILLRAMIH; break;
case COCO_FILL_RAMO: cmd = COCO_FILLRAMO; break;
}
/* fill buffer with contents of Internal Ram */
for ( i = 0; i < size; i+=2 ) {
cocoCommand (cp, COCO_SETADDR, buf[i]);
cocoCommand (cp, cmd, buf[i+1] );
}
}
/*************************************************************************
*** c o c o R e a d E x t R a m ***
*************************************************************************
*
* Name: cocoReadExtRam
*
* Purpose: Read Chameleon's External LUT into given buffer.
*
* Returns: None.
*
*************************************************************************/
static void
cocoReadExtRam ( card_t *cp, uint_t *buf, int size )
{
register uint_t cmd;
register int i;
/* fill External Ram with contents of the buffer */
for ( i = 0; i < size; i++ ) {
cocoCommand (cp, COCO_SETADDR, i);
cocoCommand (cp, COCO_READRAML, 0x0 );
buf[i] = cocoReadAmccFifo (cp);
}
}
/*************************************************************************
*** c o c o W r i t e E x t R a m ***
*************************************************************************
*
* Name: cocoWriteExtRam
*
* Purpose: Write Chameleon's External LUT into given buffer.
*
* Returns: None.
*
*************************************************************************/
static void
cocoWriteExtRam ( card_t *cp, uint_t *buf, int size )
{
register int i;
/* fill buffer with contents of Internal Ram */
for ( i = 0; i < size; i+=2 ) {
cocoCommand (cp, COCO_SETADDR, buf[i]);
cocoCommand (cp, COCO_FILLRAML, buf[i+1] );
}
}
/*************************************************************************
*** c o c o C o n v e r t ***
*************************************************************************
*
* Name: cocoConvert
*
* Purpose: Converts a single value
*
* Returns: None
*
*************************************************************************/
static void
cocoConvert ( card_t *cp, uint_t val )
{
cocoCommand ( cp, COCO_CONVERT, val );
}
/*************************************************************************
*** c o c o C o n v e r t T e s t ***
*************************************************************************
*
* Name: cocoConvertTest
*
* Purpose: Converts a single value, read the converted value back
* and compare to known value.
*
* Returns: None
*
*************************************************************************/
static void
cocoConvertTest ( card_t *cp, coco_convert_t *cv )
{
register uint_t res;
/* convert the pixle value */
cv->result = 0; /* assume success */
cocoCommand ( cp, COCO_CONVERT, cv->in );
/* read the converted value and compare */
res = cocoReadAmccFifo (cp);
if ( res != cv->out )
cv->result = 1;
}
/*************************************************************************
*** c o c o D m a T o L u t s ***
*************************************************************************
*
* Name: cocoDmaToLuts
*
* Purpose: DMAs data in user's buffer to one of Internal LUTs or
* the External LUTs (identified by cmd param). The DMA is
* done either in SINGLE or PROG (chained) mode depending
* on current set up (cp->dmatype).
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
static int
cocoDmaToLuts( card_t *cp, coco_buf_t *cb, int cmd )
{
register caddr_t kvaddr, amcc_adr;
register coco_dmapage_t *dmaPg;
register int len, page_no, s, i, dmatype, err, tot_bytes;
register int cache_bytes;
register uint_t dmabits, *ib, olddmacmd, dmacmd;
register uint_t *cache_line;
alenlist_t addrList2;
alenlist_t addrList;
size_t p_size;
alenaddr_t p_addr;
iopaddr_t p_dmaPg;
amcc_adr = cp->amcc_adr;
dmacmd = cp->dmacmd;
/* ==========================================
* Scatter-Gather list preparation
* ========================================== */
/* get the user's address and size */
len = cb->buf_size * sizeof(uint_t);
/* lock user pages into memory for DMA */
if ( cocoLockUser ( (caddr_t)cb->buf, len, B_WRITE) != 0 ) {
cmn_err (CE_WARN, "cocoDmaToLuts: Cannot lock user pages");
return (EFAULT);
}
/*
* write back and invalidate the data for mem->board
* adjust the address for current data cache size
*/
cache_line = (uint_t *)cb->buf;
cache_line = (uint_t *)( ((uint_t)cache_line / COCO_CACHE_SIZE) *
COCO_CACHE_SIZE );
cache_bytes = len + ( (uint_t)cb->buf - (uint_t)cache_line);
dki_dcache_wbinval ( (caddr_t)cache_line, cache_bytes );
/* create scatter-gather list of user's buffer */
addrList2 = uvaddr_to_alenlist( (alenlist_t)NULL, (caddr_t)cb->buf,
(size_t)len);
if ( addrList2 == (alenlist_t)NULL ) {
cmn_err (CE_WARN, "cocoDmaToLuts: cannot create alenlist");
cocoUnlockUser ( (caddr_t)cb->buf, len, B_WRITE);
return (EIO);
}
addrList = pciio_dmatrans_list ( cp->vhdl, cp->dev_desc,
/* addrList2, 0 ); */
addrList2, PCIIO_DMAMAP_BIGEND);
#if 0
page_no = alenlist_size ( addrList ); /* total pages to DMA */
#endif
page_no = cocoAlenlistSize ( addrList );
cp->page_no = page_no;
alenlist_done (addrList2);
/* initialize our DMA event semaphore */
cp->dmabits = 0;
olddmacmd = dmacmd;
dmatype = cp->dmatype;
initnsema ( &cp->dmawait, 0, "coco" );
cocoResetAmcc ( cp );
cocoPrepAmcc ( cp );
/* ===============================
* Set proper DMA flags
* =============================== */
dmacmd = DMAREG_FILL;
/* set proper LUT fill bit */
switch ( cmd ) {
case COCO_BLOCK_FILL_RAMIL:
dmacmd |= COCO_FILLRAMIL;
#ifdef DEBUG
printf (
"cocoDmaToLuts: Writing %d bytes to RAMIL, %d pages\n",
len, page_no );
#endif
break;
case COCO_BLOCK_FILL_RAMIH:
dmacmd |= COCO_FILLRAMIH;
#ifdef DEBUG
printf (
"cocoDmaToLuts: Writing %d bytes to RAMIH, %d pages\n",
len, page_no );
#endif
break;
case COCO_BLOCK_FILL_RAMO:
dmacmd |= COCO_FILLRAMO;
#ifdef DEBUG
printf (
"cocoDmaToLuts: Writing %d bytes to RAMIO, %d pages\n",
len, page_no );
#endif
break;
case COCO_BLOCK_FILL_RAML:
dmacmd |= COCO_FILLRAML;
#ifdef DEBUG
printf (
"cocoDmaToLuts: Writing %d bytes to RAML, %d pages\n",
len, page_no );
#endif
break;
}
cp->dmacmd = dmacmd;
/* =======================
* Chained DMA
* ======================= */
if ( dmatype == DMA_PROG ) {
#ifdef DEBUG
printf ("cocoDmaToLuts: Chain Dma %d pages\n", page_no );
#endif
dmaPg = cocoMakeChain ( cp, addrList, page_no );
if ( dmaPg == (coco_dmapage_t *)NULL ) {
printf ("cocoDmaLut: Error creating chain list\n");
cocoUnlockUser ( (caddr_t)cb->buf, len, B_WRITE );
alenlist_done(addrList);
return(EIO);
}
tot_bytes = page_no * sizeof(coco_dmapage_t);
p_dmaPg = pciio_dmatrans_addr ( cp->vhdl, cp->dev_desc,
kvtophys(dmaPg), tot_bytes,
PCIIO_DMAMAP_BIGEND );
#ifdef DEBUG
cocoShowChain( "Lut Chain List", dmaPg );
#endif
/* write back cache for the chain list */
dki_dcache_wbinval(dmaPg, tot_bytes );
s = COCO_LOCK();
err = 0;
/* dma from memory -> board (selected lut buffer) */
cocoStartProgDma ( cp, p_dmaPg, len, B_WRITE );
cp->dmastat = DMA_LUT_WAIT;
#ifdef DEBUG
printf ("cocoDmaToLut: Waiting DMA Interrupt\n");
#endif
if ( SleepEvent(&cp->dmawait) != 0 ) {
#ifdef DEBUG
printf ("cocoDmaToLut: Interrupted\n");
#endif
err = EINTR;
}
else {
#ifdef DEBUG
printf ("cocoDmaToLuts: Woken up..all done\n");
#endif
err = 0;
}
/* we are done */
cp->dmastat = DMA_IDLE;
cp->dmabits = 0;
cp->dmacmd = olddmacmd;
COCO_UNLOCK(s);
cocoUnlockUser ( (caddr_t)cb->buf, len, B_WRITE );
alenlist_done(addrList);
kmem_free ( dmaPg, page_no * sizeof(coco_dmapage_t) );
return(err);
}
/* ===========================
* Single page DMA
* =========================== */
for ( i = 0; i < page_no; i++ ) {
/* get a page to DMA */
if ( alenlist_get(addrList, NULL, NBPP,
&p_addr, &p_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN, "cocoDma: Bad scatter-gather");
cocoUnlockUser ( (caddr_t)cb->buf, len, B_WRITE );
alenlist_done(addrList);
return(ENOMEM);
}
#ifdef DEBUG
printf (
"cocoDmaToLuts: Loop ...DMA page %d of %d (%d bytes), p_addr = 0x%x\n",
i+1, page_no, p_size, p_addr );
#endif
s = COCO_LOCK();
err = 0;
cp->dmastat = DMA_LUT_WAIT;
cocoStartSingleDma ( cp, p_addr, p_size, B_WRITE );
#ifdef DEBUG
printf ("cocoDmaToLut: Loop, waiting for Interrupt\n");
#endif
if ( SleepEvent(&cp->dmawait) != 0 ) {
#ifdef DEBUG
printf ("cocoDmaToLut: Interrupted...\n");
#endif
err = EINTR;
COCO_UNLOCK(s);
break;
}
else {
COCO_UNLOCK(s);
#ifdef DEBUG
printf ("cocoDmaToLut: Loop ..Woken up\n");
#endif
}
}
#ifdef DEBUG
printf ("cocoDmaToLut: DMA is done\n");
#endif
/* we are done */
cp->dmastat = DMA_IDLE;
cp->dmabits = 0;
cp->dmacmd = olddmacmd;
cocoUnlockUser ( (caddr_t)cb->buf, len, B_WRITE );
alenlist_done(addrList);
return(err);
}
/*************************************************************************
*** c o c o L o c k U s e r ***
*************************************************************************
*
* Name: cocoLockUser
*
* Purpose: Given a user's address space pointer, it locks all user's
* pages in memory in preparation for DMA.
*
* Returns: 0 = Success or errno
*
*************************************************************************/
static int
cocoLockUser ( caddr_t user_buf, int len, int direction )
{
/* lock pages in memory */
if ( userdma( (caddr_t)user_buf, len, direction) == 0 )
return (EFAULT);
return (0);
}
/*************************************************************************
*** c o c o U n l o c k U s e r ***
*************************************************************************
*
* Name: cocoUnlockUser
*
* Purpose: Given a user's address, unlock all pages for that address
*
* Returns: None.
*
*************************************************************************/
static void
cocoUnlockUser ( caddr_t user_buf, int len, int direction )
{
undma ( user_buf, len, direction );
}
/*************************************************************************
*** c o c o P r e p A m c c ***
*************************************************************************
*
* Name: cocoPrepAmcc
*
* Purpose: Prepares AMCC chip for DMA.
*
* Returns: None.
*
*************************************************************************/
static void
cocoPrepAmcc ( card_t *cp )
{
register caddr_t adr_amcc;
register uint_t mcsr, intcsr;
register uint_t tmp;
adr_amcc = cp->amcc_adr;
/* ===========================
* Prepare AMCC chip
* =========================== */
/*
* Prepare AMCC as follow:
* - Read Maibox registers to make sure they are empty
* - Set Amcc DMA count to 0
* - Enable Amcc Read/Write interrupts
*/
tmp = Inp32(adr_amcc+AMCC_OP_REG_IMB4); /* Read In mbox */
tmp = Inp32(adr_amcc+AMCC_OP_REG_OMB4); /* read Out mbox */
Out32(adr_amcc+AMCC_OP_REG_MRTC, 0 );
Out32(adr_amcc+AMCC_OP_REG_MWTC, 0 );
Out32(adr_amcc+AMCC_OP_REG_INTCSR, AMCC_INTCSR_MASK );
}
/*************************************************************************
*** c o c o R e s e t A m c c ***
*************************************************************************
*
* Name: cocoResetAmcc
*
* Purpose: Resets Addon and Amcc Fifos
*
* Returns: None.
*
*************************************************************************/
static void
cocoResetAmcc ( card_t *cp )
{
register caddr_t adr_amcc;
adr_amcc = cp->amcc_adr;
/* Reset Amcc Fifos and Addon interface */
Out32(adr_amcc+AMCC_OP_REG_MCSR, AMCC_RST_ADDON );
Out32(adr_amcc+AMCC_OP_REG_MCSR, AMCC_RST_FIFOS );
}
/*************************************************************************
*** c o c o S t a r t P r o g D m a ***
*************************************************************************
*
* Name: cocoStartProgDma
*
* Purpose: Programs the board for Chained DMA and starts the Dma
*
* Returns: None.
*
*************************************************************************/
static void
cocoStartProgDma ( card_t *cp, iopaddr_t p_dmaPg, int tot_bytes, int rw )
{
register caddr_t adr_cfg, adr_norm, adr_amcc;
register uint_t adr_bits, enable_bits, dmacfg, dmacmd, dmabits;
adr_cfg = cp->conf_adr;
adr_norm = cp->norm_adr;
adr_amcc = cp->amcc_adr;
dmacfg = cp->dmacfg;
dmacmd = cp->dmacmd;
dmabits = cp->dmabits;
cp->dmasize = tot_bytes;
#ifdef DEBUG
cocoReport ( cp, "before cocoStartProgDma");
#endif
/* clear eof marks */
Out32(adr_cfg, dmacfg );
/* ======================
* board -> memory
* ====================== */
if ( rw == B_READ ) {
/* set Amcc counters */
Out32(adr_amcc+AMCC_OP_REG_MWTC, tot_bytes );
Out32(adr_amcc+AMCC_OP_REG_MRTC, 0 );
adr_bits = DMAREG_RAMPRWR;
enable_bits = DMAREG_INTPWEN | DMAREG_WEN;
dmabits |= DMAREG_PWEN;
}
/* ======================
* memory -> board
* ====================== */
else {
/* set Amcc counters */
Out32(adr_amcc+AMCC_OP_REG_MRTC, tot_bytes );
Out32(adr_amcc+AMCC_OP_REG_MWTC, 0 );
adr_bits = DMAREG_RAMPRRD;
enable_bits = DMAREG_INTPREN | DMAREG_REN;
dmabits |= DMAREG_PREN;
}
/* enable chaining */
dmacfg |= dmabits;
Out32(adr_cfg, dmacfg );
/* set address of chained list */
Out32(adr_cfg, dmacfg | DMAREG_RAM | adr_bits );
Out32(adr_norm, p_dmaPg);
/* start the DMA */
dmabits |= enable_bits | dmacmd;
cp->dmabits = dmabits;
#ifdef DEBUG
cocoReport ( cp, "after cocoStartProgDma");
#endif
pciio_flush_buffers ( cp->vhdl );
microtime ( &cp->start_time );
Out32(adr_cfg, dmacfg | dmabits );
}
/*************************************************************************
*** c o c o S t a r t S i n g l e D m a ***
*************************************************************************
*
* Name: cocoStartSingleDma
*
* Purpose: Programs the board for Single page DMA (read or write)
*
* Returns: None.
*
*************************************************************************/
static void
cocoStartSingleDma ( card_t *cp, alenaddr_t p_addr, size_t p_size, int rw )
{
register caddr_t adr_cfg, adr_norm, adr_amcc;
register uint_t adr_bits, enable_bits, dmacfg, dmacmd, dmabits, temp;
register int tot_words;
adr_cfg = cp->conf_adr;
adr_norm = cp->norm_adr;
adr_amcc = cp->amcc_adr;
dmacfg = cp->dmacfg;
dmacmd = cp->dmacmd;
dmabits = cp->dmabits;
enable_bits = 0;
cp->dmasize = p_size;
#ifdef DEBUG
printf ("cocoStartSingleDma: Dma'ing %d bytes %s, p_addr = 0x%x\n",
p_size, rw==B_READ ? "from board":"to board", p_addr );
#endif
Out32(adr_cfg, dmacfg ); /* clear eof marks */
/*
* Select read/write count in DMA controller
* and set the DMA size in 32-bit values
*/
tot_words = (int)p_size/sizeof(uint_t);
if ( rw == B_READ ) { /* board -> memory */
Out32(adr_amcc+AMCC_OP_REG_MWTC, (uint_t)p_size );
Out32(adr_amcc+AMCC_OP_REG_MRTC, 0xffffffff );
/* Out32(adr_amcc+AMCC_OP_REG_MRTC, (uint_t)p_size ); */
dmabits |= DMAREG_WEN;
Out32(adr_cfg, dmacfg | DMAREG_WCNT);
}
else {
Out32(adr_amcc+AMCC_OP_REG_MRTC, (uint_t)p_size );
Out32(adr_amcc+AMCC_OP_REG_MWTC, 0xffffffff );
/* Out32(adr_amcc+AMCC_OP_REG_MWTC, (uint_t)p_size ); */
dmabits |= DMAREG_REN;
Out32(adr_cfg, dmacfg | DMAREG_RCNT);
}
/* set transfer counts in words - -1 since counts down to 0xffff */
tot_words--;
Out32(adr_norm, (uint_t)tot_words);
/* select Read/Write Address and set the DMA address */
if ( rw == B_READ ) {
Out32(adr_cfg, dmacfg | DMAREG_RAM | DMAREG_RAMWADR); /* b->m */
}
else {
Out32(adr_cfg, dmacfg | DMAREG_RAM | DMAREG_RAMRADR); /* m->b */
}
Out32(adr_norm, (uint_t)p_addr );
/* enable the right interrupt */
if ( rw == B_READ )
dmabits |= DMAREG_INTWEN; /* board -> memory */
else dmabits |= DMAREG_INTREN; /* memory -> board */
dmabits |= cp->dmacmd;
cp->dmabits = dmabits;
#ifdef DEBUG
cocoReport ( cp, "cocoStartSingleDma exit");
#endif
pciio_flush_buffers ( cp->vhdl );
microtime ( &cp->start_time);
Out32(adr_cfg, dmacfg | dmabits );
}
/*************************************************************************
*** c o c o M a k e C h a i n ***
*************************************************************************
*
* Name: cocoMakeCHain
*
* Purpose: given an alenlist, creates chained list for Prog DMA.
*
* Returns: NULL for error or address of chained list
*
*************************************************************************/
static coco_dmapage_t *
cocoMakeChain ( card_t *cp, alenlist_t addrList, int page_no )
{
register coco_dmapage_t * dmaPg;
register int i, tot_words;
size_t p_size;
alenaddr_t p_addr;
iopaddr_t pa;
dmaPg = (coco_dmapage_t *)kmem_alloc (page_no * sizeof(coco_dmapage_t),
KM_NOSLEEP | KM_PHYSCONTIG | KM_CACHEALIGN);
if ( dmaPg == (coco_dmapage_t *)NULL ) {
printf ("cocoMakeChain: Not enough mem for chained list\n");
return( (coco_dmapage_t *)NULL);
}
/* fill the chained list with address-size values */
for ( i = 0; i < page_no; i++ ) {
if ( alenlist_get(addrList, NULL, NBPP,
&p_addr, &p_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN, "cocoMakeChain: Bad alenlist\n");
return( (coco_dmapage_t *)NULL);
}
pa = pciio_dmatrans_addr ( cp->vhdl, cp->dev_desc,
kvtophys(&dmaPg[i+1]),
sizeof(coco_dmapage_t),
PCIIO_DMAMAP_BIGEND );
dmaPg[i].nextaddr = (paddr_t)pa;
dmaPg[i].addr = (paddr_t)p_addr;
tot_words = (int)p_size/sizeof(uint_t);
dmaPg[i].size = tot_words - 1;
}
dmaPg[i-1].size |= END_OF_CHAIN;
return ( dmaPg );
}
/*************************************************************************
*** c o c o M a k e C h a i n R W ***
*************************************************************************
*
* Name: cocoMakeCHainRW
*
* Purpose: Creates chain list for simultaneous read and write
* but makes sure we read and write the same amount of bytes
* in each entry
*
* Returns: 0 = Success, or errno
*
*************************************************************************/
static int
cocoMakeChainRW ( card_t *cp,
coco_dmapage_t **w_dmaPg,
coco_dmapage_t **r_dmaPg )
{
register coco_dmapage_t *wp, *rp;
register alenaddr_t wp_resadr, rp_resadr;
register iopaddr_t pa;
register size_t wp_left, rp_left;
register int i, tot_rchain, tot_wchain, w_page, r_page;
register int tot_bytes, tot_words;
size_t wp_size, rp_size;
alenaddr_t wp_addr, rp_addr;
w_page = cp->w_page_no;
r_page = cp->r_page_no;
tot_rchain = r_page * CHAIN_FACTOR;
tot_wchain = w_page * CHAIN_FACTOR;
wp_resadr = 0;
rp_resadr = 0;
wp_left = 0;
rp_left = 0;
*w_dmaPg = (coco_dmapage_t *)NULL;
*r_dmaPg = (coco_dmapage_t *)NULL;
/* allocate chain list for Write */
wp = (coco_dmapage_t *)kmem_alloc (tot_wchain * sizeof(coco_dmapage_t),
KM_NOSLEEP | KM_PHYSCONTIG | KM_CACHEALIGN);
if ( wp == (coco_dmapage_t *)NULL ) {
cmn_err (CE_WARN, "cocoMakeChainRW: Not enough memory");
return (ENOMEM);
}
/* allocate chain list for Read */
rp = (coco_dmapage_t *)kmem_alloc (tot_rchain * sizeof(coco_dmapage_t),
KM_NOSLEEP | KM_PHYSCONTIG | KM_CACHEALIGN);
if ( rp == (coco_dmapage_t *)NULL ) {
kmem_free (wp, tot_wchain * sizeof(coco_dmapage_t) );
cmn_err (CE_WARN, "cocoMakeChainRW: Not enough memory");
return (ENOMEM);
}
/* make sure we are at the top of the list */
alenlist_cursor_init ( cp->r_addrList, NULL, NULL );
alenlist_cursor_init ( cp->w_addrList, NULL, NULL );
#ifdef DEBUG
printf ("cocoMakeChainRW: ----- Started -----\n");
cocoShowAlenlist ( "Write Alenlist", cp->w_addrList );
cocoShowAlenlist ( "Read Alenlist", cp->r_addrList );
#endif
for ( i = 0;; i++ ) {
/* ================================
* Write address and count
* ================================ */
/* if no Write bytes left, get a new page */
if ( wp_resadr == (alenaddr_t)NULL ) {
/* we must have data for write */
if ( w_page <= 0 ) {
cmn_err (CE_WARN,
"cocoMakeChainRW: Premature end of Write, w_page = %d, r_page = %d",
w_page, r_page );
#ifdef DEBUG4
printf ("%d Write pages, %d Read pages\n",
tot_wchain / CHAIN_FACTOR,
tot_rchain / CHAIN_FACTOR );
#ifdef DEBUG3
rp[i-1].size |= END_OF_CHAIN;
wp[i-1].size |= END_OF_CHAIN;
cocoShowChain ("Write Chain", wp );
cocoShowChain ("Read Chain", rp );
#endif
cocoShowAlenlist ( "Write Alenlist", cp->w_addrList );
cocoShowAlenlist ( "Read Alenlist", cp->r_addrList );
#endif
kmem_free (wp, tot_wchain * sizeof(coco_dmapage_t) );
kmem_free (rp, tot_rchain * sizeof(coco_dmapage_t) );
return(EIO);
}
if ( alenlist_get(cp->w_addrList, NULL, NBPP,
&wp_addr, &wp_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN,
"cocoMakeChainRW: Bad Write alenlist\n");
kmem_free (wp, tot_wchain * sizeof(coco_dmapage_t) );
kmem_free (rp, tot_rchain * sizeof(coco_dmapage_t) );
return(EIO);
}
w_page--;
}
/* some old Write bytes left - use those */
else {
#ifdef DEBUG
printf ("using %d old Write bytes left at 0x%x\n",
wp_left, wp_resadr );
#endif
wp_addr = wp_resadr;
wp_size = wp_left;
}
/* ================================
* Read address and count
* ================================ */
/* if no Read bytes left, get a new page */
if ( rp_resadr == (alenaddr_t)NULL ) {
/* we must have data for read */
if ( r_page <= 0 ) {
cmn_err (CE_WARN,
"cocoMakeChainRW: Premature end of Read, r_page = %d, w_page = %d",
r_page, w_page );
#ifdef DEBUG4
printf ("%d Write pages, %d Read pages\n",
tot_wchain / CHAIN_FACTOR,
tot_rchain / CHAIN_FACTOR );
#ifdef DEBUG3
wp[i-1].size |= END_OF_CHAIN;
rp[i-1].size |= END_OF_CHAIN;
cocoShowChain ("Read Chain", rp );
cocoShowChain ("Write Chain", wp );
#endif
cocoShowAlenlist ( "Write Alenlist", cp->w_addrList );
cocoShowAlenlist ( "Read Alenlist", cp->r_addrList );
#endif
kmem_free (wp, tot_wchain * sizeof(coco_dmapage_t) );
kmem_free (rp, tot_rchain * sizeof(coco_dmapage_t) );
return(EIO);
}
if ( alenlist_get(cp->r_addrList, NULL, NBPP,
&rp_addr, &rp_size) != ALENLIST_SUCCESS ) {
cmn_err (CE_WARN,
"cocoMakeChainRW: Bad Read alenlist\n");
kmem_free (wp, tot_wchain * sizeof(coco_dmapage_t) );
kmem_free (rp, tot_rchain * sizeof(coco_dmapage_t) );
return(EIO);
}
r_page--;
}
/* some old Read bytes left - use those */
else {
#ifdef DEBUG
printf ("using %d old Read bytes left at 0x%x\n",
rp_left, rp_resadr );
#endif
rp_addr = rp_resadr;
rp_size = rp_left;
}
/* ====================================
* Adjust Read and Write counts
* ==================================== */
wp_resadr = 0;
rp_resadr = 0;
wp_left = 0;
rp_left = 0;
/* Writing and Reading the same amount ? no adjustment */
if ( wp_size == rp_size ) {
#ifdef DEBUG
printf (
"Same byte count (%d) at read 0x%x and write 0x%x ..no adjustment\n",
wp_size, rp_addr, wp_addr );
#endif
tot_bytes = wp_size;
}
/* Writing more than reading ? Adjust Write */
if ( wp_size > rp_size ) {
tot_bytes = rp_size;
wp_resadr = (alenaddr_t)((int)wp_addr + tot_bytes);
wp_left = wp_size - tot_bytes;
#ifdef DEBUG
printf ("Write adjusted ..%d bytes left at 0x%x\n",
wp_left, wp_resadr );
#endif
}
/* Reading more than write ? Adjust Read */
if ( rp_size > wp_size ) {
tot_bytes = wp_size;
rp_resadr = (alenaddr_t)((int)rp_addr + tot_bytes);
rp_left = rp_size - tot_bytes;
#ifdef DEBUG
printf ("Read adjusted ..%d bytes left at 0x%x\n",
rp_left, rp_resadr );
#endif
}
/* =======================================
* Make Read and Write Chain Entries
* ======================================= */
tot_words = (int)tot_bytes/sizeof(uint_t);
tot_words--;
/* Make Write Chain entry */
pa = pciio_dmatrans_addr ( cp->vhdl, cp->dev_desc,
kvtophys(&wp[i+1]),
sizeof(coco_dmapage_t),
PCIIO_DMAMAP_BIGEND );
wp[i].nextaddr = (paddr_t)pa;
wp[i].addr = (paddr_t)wp_addr;
wp[i].size = tot_words;
/* Make Write Chain entry */
pa = pciio_dmatrans_addr ( cp->vhdl, cp->dev_desc,
kvtophys(&rp[i+1]),
sizeof(coco_dmapage_t),
PCIIO_DMAMAP_BIGEND );
rp[i].nextaddr = (paddr_t)pa;
rp[i].addr = (paddr_t)rp_addr;
rp[i].size = tot_words;
/* end of Write chain list ? */
if ( (w_page <=0) && (wp_resadr == (alenaddr_t)NULL) )
wp[i].size |= END_OF_CHAIN;
/* end of Read chain list ? */
if ( (r_page <=0) && (rp_resadr == (alenaddr_t)NULL) )
rp[i].size |= END_OF_CHAIN;
/* end of loop ? */
if ( (rp[i].size & END_OF_CHAIN) &&
(wp[i].size & END_OF_CHAIN) )
break;
/* ran out of memory ? */
if ( (i >= tot_rchain) || (i >= tot_wchain) ) {
cmn_err (CE_WARN,
"cocoMakeChainRW: Ran out of Chain entries");
kmem_free (wp, tot_wchain * sizeof(coco_dmapage_t) );
kmem_free (rp, tot_rchain * sizeof(coco_dmapage_t) );
return(EIO);
}
} /*** for ( i = 0;; i++ ) ***/
*w_dmaPg = wp;
*r_dmaPg = rp;
return(0);
}
/*************************************************************************
*** c o c o T i m e O u t ***
*************************************************************************
*
* Name: cocoTimeOut
*
* Purpose: We timedout waiting for a read/write interrupt.
*
* Returns: None.
*
*************************************************************************/
static void
cocoTimeOut ( card_t *cp )
{
register uint_t intcsr;
cmn_err (CE_NOTE, "cocoTimeOut: Read/Write Timed out");
alenlist_done ( cp->addrList );
cp->addrList = 0;
#ifdef DEBUG
cocoDumpAmcc(cp);
#endif
bioerror ( cp->bp, ETIME);
biodone (cp->bp);
}
/*************************************************************************
*** c o c o T i m e O u t 2 ***
*************************************************************************
*
* Name: cocoTimeOut2
*
* Purpose: We timedout waiting for a simultaneous read/write intr.
*
* Returns: None.
*
*************************************************************************/
static void
cocoTimeOut2 ( card_t *cp )
{
cmn_err (CE_NOTE, "cocoTimeOut2: Sim. Read/Write Timed out");
#ifdef DEBUG
cocoDumpAmcc(cp);
#endif
cp->iostat = IO_TIME;
WakeEvent(&cp->dmawait);
}
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~ ~~~~~~~~~~~~~
~~~~~~~~~~ D e b u g i n g R o u t i n e s ~~~~~~~~~~~~~~
~~~~~~~~~~ ~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/*************************************************************************
*** c o c o I o c t l S t r ***
*************************************************************************
*
* Name: cocoIoctlStr
*
* Purpose: return string version of an Ioctl command
*
* Returns: pointer to string
*
*************************************************************************/
static char *
cocoIoctlStr ( int cmd )
{
switch ( cmd ) {
case COCO_COMMAND: return ("Coco_Command");
case COCO_RAW_READ_FIFO: return ("Raw_Read_Fifo");
case COCO_RAW_WRITE_FIFO: return ("Raw_Write_Fifo");
case COCO_RAW_READB_FIFO: return ("Raw_ReadB_Fifo");
case COCO_RAW_WRITEB_FIFO: return ("Raw_WriteB_Fifo");
case COCO_FIFO_TEST: return ("Fifo_Test");
case COCO_SET_MODE: return ("Set_Mode");
case COCO_READ_MODE: return ("Read_Mode");
case COCO_RAW_READ_DMA: return ("Raw_Read_Dma");
case COCO_RAW_WRITE_DMA: return ("Raw_Write_Dma");
case COCO_READ_ADDR: return ("Read_Addr");
case COCO_SET_ADDR: return ("Set_Addr");
case COCO_DMAREGS_TEST: return ("DmaRegs_Test");
case COCO_INTRAM_TEST: return ("IntRam_Test");
case COCO_EXTRAM_TEST: return ("ExtRam_Test");
case COCO_READ_RAMIL: return ("Read_RamIL");
case COCO_READ_RAMIH: return ("Read_RamIH");
case COCO_READ_RAMO: return ("Read_RamO");
case COCO_READ_RAML: return ("Read_RamL");
case COCO_FILL_RAMIL: return ("Fill_RamIL");
case COCO_FILL_RAMIH: return ("Fill_RamIH");
case COCO_FILL_RAMO: return ("Fill_RamO");
case COCO_FILL_RAML: return ("Fill_RamL");
case COCO_CONVERT_PIXLE: return ("Convert_Pixle");
case COCO_CONVERT_TEST: return ("Convert_Test");
case COCO_SET_SINGLE_DMA: return ("Set_Single_Dma");
case COCO_SET_PROG_DMA: return ("Set_Prog_Dma");
case COCO_BLOCK_FILL_RAMIL: return ("Block_Fill_RamIL");
case COCO_BLOCK_FILL_RAMIH: return ("Block_Fill_RamIH");
case COCO_BLOCK_FILL_RAML: return ("Block_Fill_RamL");
case COCO_BLOCK_FILL_RAMO: return ("Block_Fill_RamO");
case COCO_SETCMD_TRANSP: return ("SetCmd_Transp");
case COCO_SETCMD_CONVERT: return ("SetCmd_Convert");
case COCO_RESET: return ("Reset");
case COCO_RW_BUF: return ("RW_Buff");
case COCO_ISPCI: return ("Is_PCI");
defaults: return ("Unknown");
}
}
/*************************************************************************
*** c o c o R e p o r t ***
*************************************************************************
*
* Name: cocoReport
*
* Purpose: Prints out the bit setting in dmacfg and other status
*
* Returns: None.
*
*************************************************************************/
static void
cocoReport ( card_t *cp, char *s )
{
register uint_t dmacfg, dmabits, dmacmd, ram;
register int dmastat, dmatype;
dmacmd = cp->dmacmd;
dmabits = cp->dmabits;
dmastat = cp->dmastat;
dmatype = cp->dmatype;
printf ("%s: ", s );
printf ("dmabits: " );
if ( dmabits & DMAREG_WEN ) printf ("|Wen");
if ( dmabits & DMAREG_REN ) printf ("|Ren");
if ( dmabits & DMAREG_PREN) printf ("|Pren");
if ( dmabits & DMAREG_PWEN) printf ("|Pwen");
if ( dmabits & DMAREG_FILL) printf ("|Reg_Fill");
if ( dmabits & DMAREG_INTWEN) printf ("|IntWen");
if ( dmabits & DMAREG_INTREN) printf ("|IntRen");
if ( dmabits & DMAREG_INTPWEN ) printf ("|IntPWen");
if ( dmabits & DMAREG_INTPREN ) printf ("|IntPRen");
ram = dmabits & 0x0f000000;
if ( ram == COCO_FILLRAML) printf ("|Fill_RamL");
if ( ram == COCO_FILLRAMIL) printf ("|Fill_RamIL");
if ( ram == COCO_FILLRAMIH) printf ("|Fill_RamIH");
if ( ram == COCO_FILLRAMO) printf ("|Fill_RamO");
if ( ram == COCO_TRANSP) printf ("|Transp");
if ( ram == COCO_CONVERT) printf ("|Convrt");
printf (" dmastat: ", dmastat);
if ( dmastat == DMA_IDLE ) printf ("Idle");
if ( dmastat == DMA_LUT_WAIT ) printf ("Dma_Lut_Wait");
if ( dmastat == DMA_READ_WAIT ) printf ("Dma_Read_Wait");
if ( dmastat == DMA_WRITE_WAIT ) printf ("Dma_Write_Wait");
if ( dmastat == DMA_RW_WAIT ) printf ("Dma_RW_Wait");
printf ("\n");
}
/*************************************************************************
*** c o c o S h o w C h a i n ***
*************************************************************************
*
* Name: cocoShowChain
*
* Purpose: Displays contents of a chain list
*
* Returns: None.
*
*************************************************************************/
static void
cocoShowChain( char *title, coco_dmapage_t *dmaPg )
{
register int i, tot_bytes, tot_words;
register long all_bytes;
printf ("--- %s ---\n", title );
all_bytes = 0;
for (i = 0;; i++ ) {
tot_words = dmaPg[i].size;
tot_bytes = ( (tot_words &~END_OF_CHAIN) +1) * sizeof(uint_t);
all_bytes += tot_bytes;
printf ("addr = 0x%x, size = %d next addr = 0x%x %s\n",
dmaPg[i].addr, tot_bytes, dmaPg[i].nextaddr,
tot_words & END_OF_CHAIN ? "[ End ]":" " );
if ( tot_words & END_OF_CHAIN )
break;
}
printf ("--- Total of %d entries, %d bytes ---\n\n", i, all_bytes );
}
/*************************************************************************
*** c o c o D u m p A m c c ***
*************************************************************************
*
* Name: cocoDumpAmcc
*
* Purpose: Dumps Amcc registers for debugging purpose.
*
* Returns: None.
*
*************************************************************************/
static void
cocoDumpAmcc ( card_t *cp )
{
register caddr_t amcc_adr, norm_adr, cfg_adr;
register uint_t dmacfg, xil_stat, war, wcnt, rar, rcnt, mbef;
amcc_adr = cp->amcc_adr;
norm_adr = cp->norm_adr;
cfg_adr = cp->conf_adr;
dmacfg = cp->dmacfg;
/* disable any Dma and read in Xilinx status */
Out32(cfg_adr, dmacfg | DMAREG_STAT );
xil_stat = Inp32(norm_adr);
war = Inp32(amcc_adr+AMCC_OP_REG_MWAR);
wcnt = Inp32(amcc_adr+AMCC_OP_REG_MWTC);
rar = Inp32(amcc_adr+AMCC_OP_REG_MRAR);
rcnt = Inp32(amcc_adr+AMCC_OP_REG_MRTC);
mbef = Inp32(amcc_adr+AMCC_OP_REG_MBEF);
wcnt &= 0x01ffffff;
rcnt &= 0x01ffffff;
printf ("*** cocoDumpAmcc ***\n");
printf ("WAR = 0x%x WTC = %d [ 0x%x ]\n", war, wcnt, wcnt );
printf ("RAR = 0x%x RTC = %d [ 0x%x ]\n", rar, rcnt, rcnt );
printf ("MBEF = 0x%x Xilinx = 0x%x\n", mbef, xil_stat );
}
/*************************************************************************
*** c o c o S h o w A l e n l i s t ***
*************************************************************************
*
* Name: cocoShowAlenlist
*
* Purpose: Displayes the contents of a given Alenlist
*
* Returns: None.
*
*************************************************************************/
static void
cocoShowAlenlist ( caddr_t title, alenlist_t al )
{
size_t size;
long tot_bytes;
alenaddr_t addr;
register int count, i;
/* reset the cursor for the alenlist */
alenlist_cursor_init ( al, NULL, NULL );
printf ("cocoShowAlenlist: --- %s ---\n", title );
tot_bytes = 0;
count = 0;
for ( ;; ) {
if ( alenlist_get(al, NULL, NBPP, &addr, &size) !=
ALENLIST_SUCCESS ) {
break;
}
printf ("addr = 0x%x, size = %d ...[%d]\n",addr, size, count );
tot_bytes += size;
count++;
}
printf ("--- Total of %d bytes [ %d entries ]---\n\n",
tot_bytes, count );
/* reset the cursor now */
alenlist_cursor_init ( al, NULL, NULL );
}
/*************************************************************************
*** c o c o A l e n l i s t S i z e ***
*************************************************************************
*
* Name: cocoAlenlistSize
*
* Purpose: Returns number of pairs in a given alenlist.
*
* Returns: Number of address/size entries
*
*************************************************************************/
static int
cocoAlenlistSize ( alenlist_t al )
{
register int count;
size_t size;
alenaddr_t addr;
alenlist_cursor_init ( al, NULL, NULL );
count = 0;
for ( ;; ) {
if ( alenlist_get(al, NULL, NBPP, &addr, &size) !=
ALENLIST_SUCCESS ) {
break;
}
count++;
}
alenlist_cursor_init ( al, NULL, NULL );
return (count);
}
#ifdef DEBUGTIME
/*************************************************************************
*** c o c o R e p o r t T i m e ***
*************************************************************************
*
* Name: cocoReportTime
*
* Purpose: Displays start, intr and end time of Dma
*
* Returns: None.
*
*************************************************************************/
static void
cocoReportTime ( caddr_t title, card_t *cp, int final )
{
register long s_sec, s_milsec, s_micsec;
register long i_sec, i_milsec, i_micsec;
register long e_sec, e_milsec, e_micsec;
register struct timeval *tv;
struct timeval dtv;
if ( final == 0 ) {
tv = &cp->start_time;
s_sec = tv->tv_sec & 0x000000ff;
s_milsec = tv->tv_usec / 1000;
s_micsec = tv->tv_usec % 1000;
tv = &cp->intr_time;
i_sec = tv->tv_sec & 0x000000ff;
i_milsec = tv->tv_usec / 1000;
i_micsec = tv->tv_usec % 1000;
cocoDiffTime( &cp->start_time, &cp->intr_time, &dtv );
tv = &dtv;
e_sec = tv->tv_sec & 0x000000ff;
e_milsec = tv->tv_usec / 1000;
e_micsec = tv->tv_usec % 1000;
printf (
"%s: %s, %d bytes, ..Start: %d.%d.%d, ..Intr: %d.%d.%d, ..Diff: %d.%d.%d\n",
title, cp->dmatype == DMA_PROG ? "Chain":"Single", cp->dmasize,
s_sec, s_milsec, s_micsec,
i_sec, i_milsec, i_micsec,
e_sec, e_milsec, e_micsec );
return;
}
tv = &cp->call_time;
s_sec = tv->tv_sec & 0x000000ff;
s_milsec = tv->tv_usec / 1000;
s_micsec = tv->tv_usec % 1000;
tv = &cp->ret_time;
i_sec = tv->tv_sec & 0x000000ff;
i_milsec = tv->tv_usec / 1000;
i_micsec = tv->tv_usec % 1000;
cocoDiffTime ( &cp->call_time, &cp->ret_time, &dtv );
tv = &dtv;
e_sec = tv->tv_sec & 0x000000ff;
e_milsec = tv->tv_usec / 1000;
e_micsec = tv->tv_usec % 1000;
printf ("%s: Call at %d.%d.%d, ...Ret at %d.%d.%d, ..Diff: %d.%d.%d\n",
title,
s_sec, s_milsec, s_micsec,
i_sec, i_milsec, i_micsec,
e_sec, e_milsec, e_micsec );
}
/*************************************************************************
*** c o c o D i f f T i m e ***
*************************************************************************
*
* Name: cocoDiffTime
*
* Purpose: Given two timeval struct, it calculates the difference
*
* Returns: None.
*
*************************************************************************/
static void
cocoDiffTime ( struct timeval *st, struct timeval *et, struct timeval *dt )
{
register long s_sec, s_milsec, s_micsec;
register long e_sec, e_milsec, e_micsec;
register long s_totmic, e_totmic, diff_mic;
s_sec = st->tv_sec & 0x000000ff;
s_totmic = (s_sec * 1000000) + st->tv_usec;
e_sec = et->tv_sec & 0x000000ff;
e_totmic = (e_sec * 1000000) + et->tv_usec;
diff_mic = e_totmic - s_totmic;
dt->tv_sec = diff_mic / 1000000;
dt->tv_usec = diff_mic % 1000000;
}
#endif
#define GOOD (PG_M | PG_G | PG_SV | PG_VR)
static void
cocoDebug ( coco_rw_t *rw )
{
register int tot_bytes, tot_wrong, i;
register uint_t *kvi;
/* =========================================================
* Scatter-Gather list for Write buffer (mem -> board)
* ========================================================= */
tot_bytes = rw->buf_size * sizeof(uint_t);
/* lock user's pages in memory for DMA */
if ( cocoLockUser ( (caddr_t)rw->w_buf, tot_bytes, B_WRITE) != 0 ) {
cmn_err (CE_WARN, "cocoDebug: Cannot lock user's pages");
return;
}
kvi = (uint_t *)maputokv ( (caddr_t)rw->w_buf, tot_bytes,
/* PG_UNCACHED | GOOD ); */
pte_cachebits() | GOOD );
/* check memory before the dki_dcache */
if ( kvi ) {
tot_wrong = 0;
for ( i = 0; i < rw->buf_size; i++ ) {
if ( kvi[i] != 0x0 ) {
if ( tot_wrong %100 == 0 ) {
printf (
"cocoDebug: kvi[%d] = 0x%x at 0x%x kv and 0x%x user\n",
i, kvi[i], kvi+i, rw->w_buf+i );
}
tot_wrong++;
}
}
if ( tot_wrong )
printf (
"cocoDebug BEFORE: --- %d pixles were wrong in w_buf (0x%x)\n\n",
tot_wrong, rw->w_buf );
else printf ("cocoDebug BEFORE: Ok\n\n");
}
/* write back and invalidate the data for mem->board */
dki_dcache_wbinval ( (caddr_t)rw->w_buf , tot_bytes );
/* check after dki_dcache */
if ( kvi ) {
tot_wrong = 0;
for ( i = 0; i < rw->buf_size; i++ ) {
if ( kvi[i] != 0x0 ) {
if ( tot_wrong %100 == 0 ) {
printf (
"cocoDebug: kvi[%d] = 0x%x at 0x%x kv and 0x%x user\n",
i, kvi[i], kvi+i, rw->w_buf+i );
}
tot_wrong++;
}
}
if ( tot_wrong )
printf (
"cocoDebug AFTER: --- %d pixles were wrong in w_buf (0x%x)\n\n",
tot_wrong, rw->w_buf );
else printf ("cocoDebug AFTER: Ok\n\n");
}
unmaputokv ( (caddr_t)kvi, tot_bytes );
cocoUnlockUser ( (caddr_t)rw->w_buf, tot_bytes, B_WRITE );
}
|