Physical Channel Implementation

A media driver must implement a physical channel class derived from the DMediaDriver base class.

This includes those drivers associated with fixed media, such as the internal drive, or removable media, such as a PC Card or MultiMediaCard.

DMediaDriver is an abstract class that has virtual functions that must be implemented by your derived class. The following class definition is typical:

class DMyMediaDriver : public DMediaDriver
    {
public:
    DMyMediaDriver (TInt aMediaId);
    ~DMmcMediaDriver ();
public:    
    virtual void Close();
public:    
    virtual void Disconnect(DLocalDrive* aLocalDrive, TThreadMessage*);
    virtual TInt Request(TLocDrvRequest& aRequest);
    virtual TInt PartitionInfo(TPartitionInfo& anInfo);
    virtual void NotifyPowerDown();
    virtual void NotifyEmergencyPowerDown();
public:
    TInt DoCreate(TInt aMediaId);
    };
      

All the functions except the constructor and DoCreate() either implement or re-implement virtual functions defined by DMediaDriver.

The framework does not require the DoCreate() function, but it is useful to implement such a function to act as a second-phase constructor in the creation of the media driver. In the example code fragments, we call DoCreate() from the PDD factory object's Create() function that is responsible for creating the media driver.

There is, of course, nothing to stop you from adding your own functions and data members, if this is appropriate for your implementation. In addition, your are also free to add other classes, functions and enums to your media driver implementation.

Constructor

The media driver object is created by your PDD factory object's implementation of the Create() function. The following is the relevant line of code:

...
//Create my DMediaDriver derived object
DMyMediaDriver* pD=new DMyMediaDriver (aMediaId);
...

Your constructor, prototyped as:

DMyMediaDriver (TInt aMediaId);

gives you the chance to do any initialisation that is safe, i.e. that cannot fail. Typically, this is the kind of initialisation that does not need to acquire resources. This is the first phase of the typical Symbian platform two-phase construction process.

DMyMediaDriver::DMyMediaDriver (TInt aMediaId)
    :DMediaDriver(aMediaId)
    {
    //…do safe initialisation here
    }
        

As this code fragment shows, you need to call the base class constructor first, forwarding the TInt aMediaId value. You do not need to do anything else with this value. Note that this value is the unique media ID used when the media driver was registered.

DoCreate() - second phase constructor

The media driver object is created by your PDD factory object's implementation of the Create() function. The following is the relevant line of code, which is called after successful creation of the media driver object:

...
// Call my media driver’s second-stage constructor
Tint r = KErrNoMemory;
    if(pD)
        {
        r = pD->DoCreate(aMediaId);
        }
...
        

This is a second-phase constructor that allows you to do more complex initialisation, and initialisation that might fail. Typically, this is initialisation that acquires resources (including memory). The outline implementation of DoCreate() is:

TInt DMyMediaDriver::DoCreate(TInt aMediaId)
    {
    TInt r = KErrNone;
    //…do complex initialisation here
    return r;
    }
        

Depending on the complexity of your initialisation, you can either do all your initialisation here, and complete immediately, or you can do the initialisation as an asynchronous operation, in which case initialisation will complete at some later time.

If you do this synchronously, then the return code should reflect the success or failure of the operation. In practice, this will almost always be KErrNone.

If you do this asynchronously, then, on completion of the initialisation processing, a call should be made to: DMediaDriver::OpenMediaDriverComplete() passing either KErrNone or one of the other system-wide codes as appropriate.

PartitionInfo() - return the partition information

Once the media driver has been successfully created and initialised, and has informed the media driver subsystem of this fact by a call to DMediaDriver::OpenMediaDriverComplete(), then the subsystem makes a call to the media driver's PartitionInfo() function to get partition information for the media device.

The prototype function is:

TInt PartitionInfo(TPartitionInfo& anInfo);

A TPartitionInfo object is passed to the function, which the media driver must fill in.

Decoding of partition information may require media access, and as such may be a long running activity. Support is provided that allows this to be done asynchronously. You use the return code from PartitionInfo() to tell the media driver subsystem which operational mode you are using:

  • return KErrNone, if the decoding operation is to be done asynchronously. Note that on completion, the asynchronous operation must call DMediaDriver::PartitionInfoComplete(), returning KErrNone, or one of the other system-wide error codes, if appropriate.

  • return a value other than KErrNone , if the decoding operation has been done synchronously. If the synchronous operation is successful, return KErrCompletion, otherwise return one of the other system-wide error codes, but not KErrNone.

Decoding simple partitions

The following example shows the implementation of a simple PartitionInfo() function. Such an implementation would be provided for non-removable media, such as internal Flash memory, where the layout is simple and known to the system.

This implementation reports a single partition with the size of the entire media. The partition expects to be mounted with the FAT filesystem.

Note that this operation is done synchronously, and the function returns KErrCompletion to indicate this.

TInt DMyMediaDriver::PartitionInfo(TPartitionInfo& aInfo)
    {
    aInfo.iPartitionCount                = 1;
    aInfo.iEntry[0].iPartitionBaseAddr    = 0;
    aInfo.iEntry[0].iPartitionLen        = TotalSizeInBytes();
    aInfo.iEntry[0].iPartitionType        = KPartitionTypeFAT12;
    
    aInfo.iMediaSizeInBytes                = TotalSizeInBytes();

    return KErrCompletion;
    }
          

Decoding FAT Partitions

More complex implementations of PartitionInfo() may be required when handling removable media or more complex internal media where the layout of the media is unknown to the system.

This example shows a typical implementation for a FAT based removable media device. Here, PartitionInfo() starts the operation, which is done asynchronously by the DoPartitionInfo() function.

Note that PartitionInfo() returns KErrNone, which tells the media driver subsystem that the operation will be done asynchronously.

Note also that on completion, DoPartitionInfo() calls PartitionInfoComplete() to tell the media driver subsystem that the operation is complete.

TInt DMyMediaDriverFlash::PartitionInfo(TPartitionInfo& aInfo)
    {
    iPartitionInfo = &anInfo                        // Store aInfo until needed

    TInt errCode = LaunchPartitionInfoRequest();    // Start the asynchronous request

    return(errCode);    // This needs to be KErrNone to indicate that the operation
                        // will be completed asynchronously (unless an error occurs launching
                        // the asynchronous request!)
    }
          

This is the function that runs asynchronously

TInt DMyMediaDriver::DoPartitionInfo()
    {
    TInt partitionCount = iPartitionInfo->iPartitionCount = 0;

    // Read of the first sector successful so check for a Master Boot Record
    if (*(TUint16*)(&iIntBuf[KMBRSignatureOffset])!=0xAA55)
        {
        return(KErrCorrupt);
        }

    // Move the partition entries to a 4 byte boundary
    memcpy(&iIntBuf[0],&iIntBuf[KMBRFirstPartitionEntry],(sizeof(TMBRPartitionEntry)<<2)); 

    // Search for a x86 default boot partition - let this be the first
    TMBRPartitionEntry *pe=(TMBRPartitionEntry*)(&iIntBuf[0]);

    TInt i;
    TInt defaultPartitionNumber=-1;
    for (i=0;i<KMaxPartitionEntries;i++,pe++)
        {
        if (pe->IsDefaultBootPartition())
            {
            SetPartitionEntry(&iPartitionInfo->iEntry[0],pe->iFirstSector,pe->iNumSectors);
            iHiddenSectors=pe->iFirstSector;
            defaultPartitionNumber=i;
            partitionCount++;
            break;
            }
        }

    // Now add any other partitions
    pe=(TMBRPartitionEntry*)(&iIntBuf[0]);     // Reset it
    for (i=0;i<KMaxPartitionEntries;i++,pe++)
        {
        if (defaultPartitionNumber==i)
            {
            continue;    // Already sorted
            }
        if (pe->IsValidDosPartition())
            {
            SetPartitionEntry(&iPartitionInfo->iEntry[partitionCount],pe->iFirstSector,pe->iNumSectors);
            if (partitionCount==0)
            iHiddenSectors=pe->iFirstSector;
            partitionCount++;
            }
        }

    if (defaultPartitionNumber==(-1) && partitionCount==0)
        {
        // Assume it has no MBR, and the Boot Sector is in the 1st sector
        SetPartitionEntry(&iPartitionInfo->iEntry[0], 0, iMediaSize);
        iHiddenSectors=0;
        partitionCount=1;
        }
        
    iPartitionInfo->iPartitionCount=partitionCount;
    iPartitionInfo->iMediaSizeInBytes=TotalSizeInBytes();

    PartitionInfoComplete(err);
    
    return(KErrNone);
    }
          

Request() - handling requests

You handle requests by implementing your media driver's Request() function. This is prototyped as:

TInt Request(TLocDrvRequest& aRequest)=0;

This function is usually called in the context of the client thread that originally initiated the I/O request to the file server, although you should never assume so. Note that you may also see the originating thread referred to as the remote thread.

The request type, as identified by the request ID, and the information associated with the request is accessed through the TLocDrvRequest object, which is passed to the Request() function. The information supplied includes offsets, data lengths, the requesting thread etc, but clearly depends on the request ID. You get the request ID by calling TLocDrvRequest::Id(), and this will be one of the DLocalDrive::TRequestId enum values.

Each request ID, as defined by DLocalDrive::TRequestId has a specific meaning. The information that is available also depends on the request ID.

Depending on the request ID, the operation can be done synchronously or asynchronously. However, it is the responsibility of the implementor of the media driver to handle the incoming requests and to handle them as appropriate to the specific media, i.e. synchronously or asynchronously.

In general, the function should return once the request is initiated. If the entire operation cannot be completed immediately, then further request processing must occur within ISRs and DFCs, i.e. using some hardware specific mechanism to indicate completion, or by the use of considerate poll timers to considerately poll the device for it’s current status, with the final request completion being done from within a DFC. The code that implements the asynchronous requests can run within its own thread, or use one of the default threads provided by the kernel (DFC queue thread 0).

The underlying media driver framework allows multiple requests to be processed simultaneously. However, other than being able to issue multiple requests, there is no inherent support in the media driver framework to support the handling of multiple requests, so such functionality must be handled by the media driver itself. The underlying media driver framework does, however, provide basic support for deferring requests for later processing should the media driver not be capable of supporting multiple requests.

ECaps

This is a request for information about the size, type, attributes etc of the media. TLocDrvRequest::RemoteDes() gives you access to the object into which the media driver should put the requested information. The object passed across is a TLocalDriveCapsV2 type, and this is passed by the media driver subsystem in the form of a package buffer, a TPckgBuf type.

In practice, you just need to use a simple cast to access the object. The following code fragment is always used:

...
if (id == DLocalDrive::ECaps)
    {
    TLocalDriveCapsV2& c = *(TLocalDriveCapsV2*)aRequest.RemoteDes();
    ...
    }
....
              

This request type is synchronous.

ERead

This is a request to read data from the media device asynchronously.

You need to start an asynchronous operation that reads TLocDrvRequest::Length() bytes from the media, starting at position TLocDrvRequest::Pos() on the media.

You transfer the data to the requesting thread's process by calling TLocDrvRequest::WriteRemote(), where the first parameter is the source descriptor representing the data you have just read. For example:

...
TPtrC8 des((const TUint8*)(iBase),len);
TInt r=iReadReq->WriteRemote(&des,0);
...
              

In this example, iBase is the location of the data that has just been read in from the device, and len is the length of this data. The code fragment also assumes that the data to be returned starts at iBase, and not at some offset from iBase.

As this is an asynchronous operation, then when all data has been transferred, the request must be completed by calling DMediaDriver::Complete(), passing the original request and a completion code.

EWrite

This is a request to write data to the media device asynchronously.

You need to start an asynchronous operation that writes TLocDrvRequest::Length() bytes to the media, starting at position TLocDrvRequest::Pos() on the media.

Before doing the write, then you need to transfer the data to be written from the requesting thread's process by calling TLocDrvRequest::ReadRemote(), where the first parameter is the target descriptor.

As this is an asynchronous operation, then when all data has been transferred, the request must be completed by calling DMediaDriver::Complete(), passing the original request and a completion code.

EFormat

This is a request to format a section of the media asynchronously.

The start position of the section to be formatted can be found by calling TLocDrvRequest::Pos(), and the number of bytes to be formatted can be found by calling TLocDrvRequest::Length().

Following a format operation, the state of the formatted section depends on the type of media. In practice, you should access locations within the specified section, so that bad regions can be detected and reported.

The length of each format request is usually based on the value of TLocalDriveCapsV2::iEraseBlockSize, as returned by the ECaps request. If you need to adjust the start address of the next format request, you can return a positive value that is interpreted as indicating the actual number of bytes formatted in the current step. This feature is useful for media that prefers format operations to be performed on specific boundaries; for example MultiMedia Cards.

As this is an asynchronous operation, then when the format operation has been done, the request must be completed by calling DMediaDriver::Complete(), passing the original request and a completion code.

EEnlarge

This is a request to enlarge the accessible range of the media asynchronously. For example, this is used on the internal RAM drive.

Calling TLocDrvRequest::Length() gives you the number of bytes by which the accessible range is to be increased.

The media attributes, as defined by the settings in TLocalDriveCapsV2::iMediaAtt returned by the ECaps request, must have KMediaAttVariableSize set, otherwise the request fails with KErrNotSupported.

As this is an asynchronous operation, then when the operation is complete, the request must be completed by calling DMediaDriver::Complete(), passing the original request and a completion code.

EReduce

This is a request to reduce the accessible range of the media asynchronously. For example, this is used on the internal RAM drive.

The range to be removed is defined as TLocDrvRequest::Length() bytes starting at TLocDrvRequest::Pos(). In effect, the request removes the section from Pos() to Pos() + Length(), and the length is reduced by Length().

The media attributes, as defined by the settings in TLocalDriveCapsV2::iMediaAtt returned by the ECaps request, must have KMediaAttVariableSize set, otherwise the request fails with KErrNotSupported.

As this is an asynchronous operation, then when the operation is complete, the request must be completed by calling DMediaDriver::Complete(), passing the original request and a completion code.

A simple implementation

TInt DMyMediaDriver::Request(TLocDrvRequest& aRequest)
    {
    TInt err = KErrNotSupported;
    TInt id  = aRequest.Id();

    if (id == DLocalDrive::ECaps)
        {
        TLocalDriveCapsV2& c = *(TLocalDriveCapsV2*)aRequest.RemoteDes();
        err = Caps(c);
        c.iSize = m.Drive()->iPartitionLen;
        c.iPartitionType = m.Drive()->iPartitionType;
        return(err);
        }

    if(iCurrentReq != NULL)
        {
        return(KMediaDriverDeferRequest);
        }

    iCurrentReq = &aRequest;

    switch(id)
        {
        case DLocalDrive::ERead:
            r = StartRead();
            break;
        case DLocalDrive::EWrite:
            r = StartWrite();
            break;
        case DLocalDrive::EFormat:
            r = StartErase();
            break;
        }

    if (err < 0)
        {
        iCurrentReq = NULL;
        DMediaDriver::Complete(aRequest, err);
        }
    
    return(err);
    }
            

This demonstrates the following behaviour:

  • The ECaps request is inherently synchronous, and must complete immediately.

  • This example only handles a single request at a time. If the media driver is busy handling a request, it can return the value KMediaDriverDeferRequest which defers the message until the current request is complete.

  • Each message is passed on to the specific function that is responsible for handling the message. This provides readability and ease of maintenance.

  • If an error occurs, the request is completed immediately with the specified error code.

The following code is the implementation of the StartWrite() function that initiates the asynchronous write operation. It gets the length and position information, and then calls DoWriteStep():

TInt DMyMediaDriver::StartWrite()
    {
    // Start an asynchronous write operation

    iCurrentPos = iCurrentReq->Pos();
    iCurrentLength = iCurrentReq->Length();

    TInt err = DoWriteStep();
        
    return(err);
    }
                

DoWriteStep() performs a single write operation. In this example, a single write operation cannot exceed the capabilities of the hardware, so the request is split up into chunks of KMyMediaDriverWriteLength bytes.

TInt DMyMediaDriver::DoWriteStep()
    {
    // Perform a single write step

    TUint8* destAddress = iBaseAddress + iCurrentPos;
    TInt writeLength = MIN(iCurrentLength, KMyMediaDriverWriteLength);
        
    TPtr8 des(iData, writeLength);
    TInt err = iCurrentReq->ReadRemote(&des, iCurrentPos - iCurrentReq->Pos());
    if (err != KErrNone)
        {
        return(err);
        }

        iCurrentPos += writeLength;
        iCurrentLength -= writeLength;

        TheHardware::StartWrite(iCurrentPos, writeLength, iData);
    }
                

The write operation to the hardware is performed by TheHardware::StartWrite(). For most hardware, completion is signalled by an interrupt, and the ISR handling the interrupt will queue a DFC. This in turn can call a function like WriteComplete() shown below:

TInt DMyMediaDriver::WriteComplete(TInt aResult)
    {
    // Called upon completion of the write operation
    // (ie – in DFC after completion interrupt or polled status completion)

    TBool completeRequest = (iCurrentLength == 0) || (aResult ! = KErrNone);
        
    if(!completeRequest)
        {
        // There is more data remaining, so write some more data…
        if((aResult = DoWriteStep()) != KErrNone)
            {
            completeRequest = ETrue;
            }
        }
        
    if(completeRequest)
        {
        // We are all done, or an error occurred…
        DMediaDriver::Complete(iCurrentReq, aResult);
        iCurrentReq = NULL;
        }
    }
                

WriteComplete() is an example of a callback or completion function, and shows how a single request may be broken up into a number of smaller chunks. The write request is only completed when the entire write operation is complete or an error occurs.

This simple example has demonstrated how a simple EWrite request may be handled. The ERead and EFormat requests are handled in exactly the same way, taking into account the message parameters shown in the previous table.

Issues about physical addresses

If the media driver can use physical addresses, you need to be aware of a number of issues.

  • The address scheme used by the hardware

    All media devices have a minimum number of bytes that they can transfer. For example, the architecture of some memory card types requires data transfer in blocks of 512 bytes. To read one byte from this type of media device, the media driver must read a block of 512 bytes and extract the byte from the block. To write one byte to a media device, the media driver must read a block of 512 bytes, change the content of the byte, and write the block to the media device.

  • Data transfer smaller than the minimum size

    If the local media subsystem receives a request to transfer data with a length smaller than the minimum transfer size, the local media subsystem does not make a physical address available to the media driver. A call to TLocDrvRequest::IsPhysicalAddress() returns false. It is considered unsafe to give access to the physical data surrounding the requested memory location.

  • Data transfer not aligned to the media device block boundary

    If the local media subsystem receives a request to transfer data, and the address on the media device is not aligned to the media device block boundary, you need to adopt the technique suggested below. The local media subsystem will make the physical address available to the media driver. A call to TLocDrvRequest::IsPhysicalAddress() returns true.

    Consider the following case. A request has been made to read 1024 bytes from a media device that has a block size of 512 bytes. The 1024 bytes start at offset +256 on the media device.

    To get the first 256 bytes, you must read the first block of 512 bytes from the media device. This can corrupt the physical memory passed in the I/O request. The solution is to read the first block from the media device into an intermediate buffer. Copy the 256 bytes from that buffer into the physical memory passed in the I/O request.

    To get the last 256 bytes, you must read the third block of 512 bytes from the media device into the intermediate buffer. Copy the 256 bytes from that buffer into the correct position in the physical memory passed in the I/O request.

    The middle 512 bytes are aligned on the media device block boundary. The media driver can read this data into the correct position in the physical memory passed in the I/O request.

  • Scatter/Gather DMA controllers

    DMA controllers can support the Scatter/Gather mode of operation. Each request in this mode of operation consists of a set of smaller requests chained together. This chain of requests is called the Scatter/Gather list. Each item in the list consists of a physical address and a length.

    Use TLocDrvRequest::GetNextPhysicalAddress() to help you populate the Scatter/Gather list.

    The following code fragment shows how you do this. The example assumes that the DMA controller supports a Scatter/Gather list with an unlimited number of entries. In practice, the number of entries in the list is finite.

    TPhysAddr physAddr; 
       TInt physLength;
       TInt err = KErrNone;
    
       while (iRemaining > 0)
          {
          err = iCurrentReq->GetNextPhysicalAddress(physAddr, physLength);
          if(err != KErrNone)
             return err;
    
          iRemaining -= physLength;
          PopulateScatterGatherList(physAddr, physLength);
          }                            
    
       return DoDataTransfer(pos, length);

See also Register media driver support for physical addresses