AppleSamplePCI.cpp   [plain text]


/*
 * This is a tiny driver that attaches to a PCI device and logs information
 * about it. It doesn't alter the device in any way. It also supports a
 * generic IOUserClient subclass that allows driver specific client code to
 * make various kinds of calls into the driver, and map shared memory
 * or portions of hardware memory.
 */

#include "AppleSamplePCI.h"
#include <IOKit/IOLib.h>
#include <IOKit/assert.h>

/* 
 * Define the metaclass information that is used for runtime
 * typechecking of IOKit objects. We're a subclass of IOService,
 * but usually we would subclass from a family class.
 */

#define super IOService
OSDefineMetaClassAndStructors( AppleSamplePCI, IOService );

bool AppleSamplePCI::start( IOService * provider )
{
    IOMemoryDescriptor *	mem;
    IOMemoryMap *		map;

    IOLog("AppleSamplePCI::start\n");

    if( !super::start( provider ))
        return( false );

    /*
     * Our provider class is specified in the driver property table
     * as IOPCIDevice, so the provider must be of that class.
     * The assert is just to make absolutely sure for debugging.
     */

    assert( OSDynamicCast( IOPCIDevice, provider ));
    fPCIDevice = (IOPCIDevice *) provider;

    /*
     * Enable memory response from the card
     */
    fPCIDevice->setMemoryEnable( true );


    /*
     * Log some info about the device
     */

    /* print all the device's memory ranges */
    for( UInt32 index = 0;
         index < fPCIDevice->getDeviceMemoryCount();
         index++ ) {

        mem = fPCIDevice->getDeviceMemoryWithIndex( index );
        assert( mem );
        IOLog("Range[%ld] %08lx:%08lx\n", index,
              mem->getPhysicalAddress(), mem->getLength());
    }

    /* look up a range based on its config space base address register */
    mem = fPCIDevice->getDeviceMemoryWithRegister(
                                  kIOPCIConfigBaseAddress0 );
    if( mem )
        IOLog("Range@0x%x %08lx:%08lx\n", kIOPCIConfigBaseAddress0,
                mem->getPhysicalAddress(), mem->getLength());

    /* map a range based on its config space base address register,
     * this is how the driver gets access to its memory mapped registers
     * the getVirtualAddress() method returns a kernel virtual address
     * for the register mapping */
    
    map = fPCIDevice->mapDeviceMemoryWithRegister(
                                  kIOPCIConfigBaseAddress0 );
    if( map ) {
        IOLog("Range@0x%x (%08lx) mapped to kernel virtual address %08x\n",
                kIOPCIConfigBaseAddress0,
                map->getPhysicalAddress(),
                map->getVirtualAddress());
        /* release the map object, and the mapping itself */
        map->release();
    }

    /* read a config space register */
    IOLog("Config register@0x%x = %08lx\n", kIOPCIConfigCommand,
          fPCIDevice->configRead32(kIOPCIConfigCommand) );

    // construct a memory descriptor for a buffer below the 4Gb line &
    // so addressable by 32 bit DMA. This could be used for a 
    // DMA program buffer for example

    IOBufferMemoryDescriptor * bmd = 
	IOBufferMemoryDescriptor::inTaskWithPhysicalMask(
				// task to hold the memory
				kernel_task, 
				// options
				kIOMemoryPhysicallyContiguous, 
				// size
				64*1024, 
				// physicalMask - 32 bit addressable and page aligned
				0x00000000FFFFF000ULL);

    if (bmd) {
	generateDMAAddresses(bmd);
    } else {
	IOLog("IOBufferMemoryDescriptor::inTaskWithPhysicalMask failed\n");
    }
    fLowMemory = bmd;
    
    /* publish ourselves so clients can find us */
    registerService();

    return( true );
}

/*
 * We'll come here when the device goes away, or the driver is unloaded.
 */
 
void AppleSamplePCI::stop( IOService * provider )
{
    IOLog("AppleSamplePCI::stop\n");
    super::stop( provider );
}

/*
 * Method to supply an IOMemoryDescriptor for the user client to map into
 * the client process. This sample just supplies all of the hardware memory
 * associated with the PCI device's Base Address Register 0.
 * In a real driver mapping hardware memory would only ever be used in some
 * limited high performance scenarios where the device range can be safely
 * accessed by client code with compromising system stability.
 */

IOMemoryDescriptor * AppleSamplePCI::copyGlobalMemory( void )
{
    IOMemoryDescriptor * memory;
    
    memory = fPCIDevice->getDeviceMemoryWithRegister( kIOPCIConfigBaseAddress0 );
    if( memory)
        memory->retain();
        
    return( memory );
}

IOReturn AppleSamplePCI::generateDMAAddresses( IOMemoryDescriptor * memDesc )
{
    // Get the physical segment list. These could be used to generate a scatter gather
    // list for hardware.

    // This is the old getPhysicalSegment() loop calling IOMemoryDescriptor,
    // it will fail (panic) on new machines with memory above the 4Gb line

    IODMACommand *	cmd;
    IOReturn            err = kIOReturnSuccess;
    IOByteCount		offset = 0;
    IOPhysicalAddress	physicalAddr;
    IOPhysicalLength	segmentLength;
    UInt32              index = 0;

    while( (physicalAddr = memDesc->getPhysicalSegment( offset, &segmentLength ))) {
	IOLog("Physical segment(%ld) %08lx:%08lx\n", index, physicalAddr, segmentLength);
	offset += segmentLength;
	index++;
    }

    // 64 bit physical address generation using IODMACommand
    do
    {
	cmd = IODMACommand::withSpecification(
	    // outSegFunc - Host endian since we read the address data with the cpu
	    // and 64 bit wide quantities
	    kIODMACommandOutputHost64, 
	    // numAddressBits
	    64, 
	    // maxSegmentSize - zero for unrestricted physically contiguous chunks
	    0,
	    // mappingOptions - kMapped for DMA addresses
	    IODMACommand::kMapped,
	    // maxTransferSize - no restriction
	    0,
	    // alignment - no restriction
	    1 );
	if (!cmd)
	{
	    IOLog("IODMACommand::withSpecification failed\n");
	    break;
	}

	// point at the memory descriptor and use the auto prepare option
	// to prepare the entire range
	err = cmd->setMemoryDescriptor(memDesc);
	if (kIOReturnSuccess != err)
	{
	    IOLog("setMemoryDescriptor failed (0x%x)\n", err);
	    break;
	}

	UInt64 offset = 0;
	while ((kIOReturnSuccess == err) && (offset < memDesc->getLength()))
	{
	    // use the 64 bit variant to match outSegFunc
	    IODMACommand::Segment64 segments[1];
	    UInt32 numSeg = 1;

	    // use the 64 bit variant to match outSegFunc
	    err = cmd->gen64IOVMSegments(&offset, &segments[0], &numSeg);
	    IOLog("gen64IOVMSegments(%x) addr 0x%qx, len 0x%qx, nsegs %ld\n",
		    err, segments[0].fIOVMAddr, segments[0].fLength, numSeg);
	}

	// if we had a DMA controller, kick off the DMA here

	// when the DMA has completed,
	
	// clear the memory descriptor and use the auto complete option
	// to complete the transaction
	err = cmd->clearMemoryDescriptor();
	if (kIOReturnSuccess != err)
	{
	    IOLog("clearMemoryDescriptor failed (0x%x)\n", err);
	}
    }
    while (false);
    if (cmd)
	cmd->release();
    // end 64 bit loop


    // 32 bit physical address generation using IODMACommand
    // any memory above 4Gb in the memory descriptor will be buffered
    // to memory below the 4G line, on machines without remapping HW support
    do
    {
	cmd = IODMACommand::withSpecification(
	    // outSegFunc - Host endian since we read the address data with the cpu
	    // and 32 bit wide quantities
	    kIODMACommandOutputHost32, 
	    // numAddressBits
	    32, 
	    // maxSegmentSize - zero for unrestricted physically contiguous chunks
	    0,
	    // mappingOptions - kMapped for DMA addresses
	    IODMACommand::kMapped,
	    // maxTransferSize - no restriction
	    0,
	    // alignment - no restriction
	    1 );
	if (!cmd)
	{
	    IOLog("IODMACommand::withSpecification failed\n");
	    break;
	}

	// point at the memory descriptor and use the auto prepare option
	// to prepare the entire range
	err = cmd->setMemoryDescriptor(memDesc);
	if (kIOReturnSuccess != err)
	{
	    IOLog("setMemoryDescriptor failed (0x%x)\n", err);
	    break;
	}

	UInt64 offset = 0;
	while ((kIOReturnSuccess == err) && (offset < memDesc->getLength()))
	{
	    // use the 32 bit variant to match outSegFunc
	    IODMACommand::Segment32 segments[1];
	    UInt32 numSeg = 1;

	    // use the 32 bit variant to match outSegFunc
	    err = cmd->gen32IOVMSegments(&offset, &segments[0], &numSeg);
	    IOLog("gen32IOVMSegments(%x) addr 0x%lx, len 0x%lx, nsegs %ld\n",
		    err, segments[0].fIOVMAddr, segments[0].fLength, numSeg);
	}

	// if we had a DMA controller, kick off the DMA here

	// when the DMA has completed,
	
	// clear the memory descriptor and use the auto complete option
	// to complete the transaction
	err = cmd->clearMemoryDescriptor();
	if (kIOReturnSuccess != err)
	{
	    IOLog("clearMemoryDescriptor failed (0x%x)\n", err);
	}
    }
    while (false);
    if (cmd)
	cmd->release();
    // end 32 bit loop

    return (err);
}