The CX2388x is a multifunction PCI interface, and provides the following functions as separate devices on the PCI bus:
- Function 0: Video--Raw pixel stream, generally used for Analog TV, S-TV, or VCR applications
- Function 1: Audio--48Khz, 16-bit PCM stereo, generally used for Analog TV, FM radio, or capture from external audio decoder
- Function 2: MPEG transport stream--Used for Digital TV or reception from hardware MPEG encoder
Function 3: Video Interface Port (VIP)--Flexible mechanism for adding on-card video components, sometimes used by vendors to attach MPEG encoders
- Function 4: General Purpose Host Port--Flexible mechanism for adding non-VIP-compliant components, sometimes used by vendors for infrared remote control interfaces
These functions enumerate as separate devices on the PCI bus, but share 32K of onboard SRAM, which is used for DMA control structures and driver-configurable DMA FIFOs. Each function uses at least one DMA channel, sometimes more (e.g. the Video function has separate channels for Y, U, and V video planes, and for VBI (CC/teletext) data). Much like the old Brooktree 878 chip, the CX2388x uses propriety RISC assembly instruction format for DMA programming. Drivers are responsible for generating DMA programs as sequences of RISC instructions that control (among other things) the locations in physical memory to/from which data is transferred. While this DMA programming mechanism is very complex, it is also very powerful and well-suited for efficient A/V streaming. DMA buffers can be of arbitrary complexity, meaning that they are not required to be physically contiguous in memory.
The biggest limitation of the CX2388x, as far as modern systems are concerned, is that its DMA program format only allows for 32-bit physical memory addresses (not including the newer CX23885, which allows 64-bit addresses). This means that on 64-bit systems with large amounts of memory, the operating system will need to translate out-of-range physical addresses into 32-bit physical addresses that the CX2388x DMA engine can understand. The cx88 driver uses the BSD bus_dma framework, which handles this behind the scenes. This translation can impose a performance penalty, especially on amd64 systems where low-memory copy buffers ("bounce buffers") are used as intermediaries between high-memory buffers and 32-bit hardware devices. Some 64-bit architectures (sparc64, sun4v) have I/O MMUs, which are address translation hardware units that FreeBSD uses to eliminate the need for bounce buffers. The newest amd64 chipsets also have I/O MMUs, but FreeBSD does not (yet) use them.
This is the foundation of the cx88 driver, and consists of the cx88.ko kernel module, which is built from the sources under the common/ subdirectory. The common layer mainly contains the core logic that client drivers need to use for basic device control (attachment/detachment) and DMA. Drivers for specific CX2388x functions (video, audio, MPEG TS, etc.) will claim cx88.ko as a dependency, and will use it for the following:
- allocating/controlling DMA channel resources in the CX2388x's onboard SRAM
- allocating and/or mapping DMA buffers
- generating DMA programs
- sharing data between sibling CX2388x functions.
The common layer uses FreeBSD's kobj mechanism to allow drivers for sibling CX2388x functions to communicate with each other to optimize allocation of the onboard SRAM. This maximizes the FIFO space that can be used for DMA, which improves resistance to bus contention. Additionally, if there is enough SRAM space, DMA programs may be stored entirely in the SRAM, which eliminates the need to fetch DMA program instructions from host memory. The common layer exports functions that allow client drivers to easily generate powerful DMA programs, and allows for DMA data buffers to either be allocated in the kernel and mmap'ed to user applications, or allocated by user applications and passed to the kernel via ioctl(2) calls.
The I2C layer consists of the cx88i2c.ko kernel module, built from the sources under the i2c/ subdirectory. It provides client drivers with control routines for the CX2388x's I2C peripheral bus, which is what most frontend devices use. It provides client drivers with functions for adding and removing devices from the bus, and performs access control for those devices. For example, the I2C layer can prevent the MPEG TS and Video client drivers from trying to use an I2C tuner device simultaneously, which could have unpredictable consequences.
The I2C layer implements FreeBSD's iicbus interface, which allows individual I2C devices to be controlled by any driver that uses that interface. cx88 client drivers can specify the name of the driver they'd like to control an I2C device when they add that device to the bus. All the cx88 client drivers so far only use the "iicdev" driver, which is included with the cx88 driver package under the iicdev/ subdirectory. This provides a simple, generic "stub" interface for user-mode access to individual I2C devices. The FreeBSD "iic" driver is gradually being improved, so the client drivers may eventually switch to iic instead. Of course it's also possible for a client driver to specify a special-purpose kernel-mode driver for a specific I2C device, but this has not yet been necessary.
Internally, the bulk of the logic in the I2C layer is devoted to marrying the iicbus interface with the CX2388x hardware I2C control facilities. All I2C transactions are hardware-controlled and interrupt-driven, meaning that the CX2388x controls the I2C data and clock lines internally instead of relying on a software interface (e.g. iicbb) to do so manually.
Client drivers are responsible for controlling individual CX2388x functions. As mentioned earlier, they will depend on the core logic in the common layer, and will most likely also depend on the I2C layer. Each function has its own set of dedicated registers for control of specific operations, and beyond a few registers that are common to all functions, they constitute the vast majority of usable CX2388x register space. Control of these registers is strictly the province of client drivers, as are the interfaces they expose to user applications. A few standard ioctl codes are defined in common/cx88_common_ioctl.h, namely for querying the PCI subsystem ID (which determines the card vendor and model), configuring devices on the I2C bus, and controlling the GPIO lines. However, it is still the responsibility of client drivers to handle these codes in their ioctl callbacks. The common and I2C layers don't actually attach to any devices, nor do they provide any device interfaces for applications to use--they simply provide functions that client drivers can use to ease the implementation of those things.
The existing cx88 client drivers are designed in such a way that responsibility all vendor-specific data, which mostly consists of frontend device configuration, is pushed out to user mode. In implementing the generic I2C, GPIO, and device ID ioctls defined in cx88_common_ioctl.h, in combination with iicdev "stubs" for each frontend device, frontend control has been placed entirely in userspace. Libtuner contains drivers for specific frontend devices which can be used with the iicdev stubs exposed by each cx88 client driver through the I2C layer.