Migration Tutorial: Demand Paging and Media Drivers

Describes how to change media drivers when demand paging is used.

Demand paging is a change made from Symbian platform v9.3 to how the Kernel uses RAM and storage media. This topic

Introduction

If the ROM has been built with paging enabled, the image is divided into two sections: an unpaged section and a paged section. In addition to this, code that is not part of the ROM can be loaded on demand into RAM from other non-XIP partitions and/or media, for example FAT or ROFS partitions.

Two types of paging are currently supported:

  • ROM paging - paging from the paged section of the ROM image

  • code paging - paging from non-removable media, for example, a FAT partition on an internal Multi Media Card (MMC) drive or an internal NAND ROFS/FAT partition.

    See also Paging from an internal MMC.

Note: : A difference between ROM paging and code paging is that all ROM pages are contiguous, whereas code that is paged from other drives may be split over several potentially non-contiguous clusters. This puts an extra burden on the paging subsystem as it needs to identify the layout of the DLL on the media before it is deemed pageable. This is achieved by using the file servers blockmap API.

Media drivers are typically PDDs with a filename of med.pdd. Normally they are declared in the rombuild.oby file with the keyword extension or device and are therefore flagged as unpaged. That is, once loaded their code and read-only data sections are not paged out.

A media driver that is capable of servicing page requests from the paging subsystem must ensure that the thread in which the media driver runs takes the page fault itself otherwise a deadlock could occur. In theory, the only time this can happen is when a media driver accepts a write request from a user side client that points to data in the paged section of the ROM or to code that has been loaded into RAM from code paging enabled media. To remedy this, the local media subsystem has been modified to lock write requests to paging media drivers before they are dispatched and to split large write requests into a series of smaller ones to avoid exhausting available RAM.

The two initial stages relevant to this discussion are:

  • the kernel extension entry point - identified by the DECLARE_STANDARD_EXTENSION macro

  • the PDD entry point - identified by the DECLARE_EXTENSION_PDD macro.

To enable demand paging as soon as possible in the boot sequence it is desirable to instantiate and install the PDD factory object earlier, for example in the kernel extension entry point.

Note: Some media drivers have no kernel extension entry point defined, the MMC media driver is an example. These drivers have a DECLARE_STANDARD_PDD macro defined rather than DECLARE_EXTENSION_PDD. They require modification to have a DECLARE_EXTENSION_PDD and a DECLARE_STANDARD_EXTENSION.

The steps needed to support ROM and/or code paging are as follows:

  1. determine whether code paging is supported, and if so, identify the relevant local drive number or numbers

  2. modify variantmediadef.h

  3. modify the media drivers kernel extension entry point to register the media driver with the paging subsystem and to instantiate and install the DPhysicalDevice derived factory object

  4. modify the DLocalDrive::Ecaps() handling

  5. modify the media drivers DMediaDriver::Request function to accept the four new paging request types

  6. Handling fragmented write requests.

Changes to variantmediadef.h

The following should be defined using appropriate names in the variant's variantmediadef.h file:

  • PAGING_TYPE - flags that indicate whether code paging and/or ROM paging is supported

  • NAND_PAGEDRIVELIST - a list of the paging drives

  • NAND_PAGEDRIVECOUNT - the total number of paging drives

  • NUM_PAGES - if a write request points to data that resides in paged ROM, the request is split up into separate fragments of the specified size. This value needs to be chosen with care, as if it is too small writes can take too long to finish.

Note: Normal write requests which point to data that is not in paged ROM are not fragmented. However, large requests are split up into smaller requests by the file server, providing clients with a more responsive system.

The macros defined in the file variantmediadef.h are passed to LocDrv::RegisterPagingDevice(). This function is similar to LocDrv::RegisterMediaDevice() in that it takes a drive list as a parameter, but it identifies the drive or drives to be used for code paging. If code only ROM paging is needed, set the drive count to zero.

Changes made to support paging on NAND:

// Variant parameters for NAND flash media driver (mednand.pdd)
#define NAND_DRIVECOUNT 8
#define NAND_DRIVELIST 2,3,5,6,7,9,10,11
#define NAND_NUMMEDIA 1
#define NAND_DRIVENAME "Nand"

#define PAGING_TYPE DPagingDevice::ERom | DPagingDevice::ECode

// code paging from writeable FAT, Composite FAT and first ROFS
#define NAND_PAGEDRIVELIST 2,5,6    
#define NAND_PAGEDRIVECOUNT 3

// defines the size of fragment
#define NUM_PAGES 8

Changes to the media driver kernel extension point

The kernel-extension entry point must create a DFC queue to satisfy any page fault that occurs in the drive thread. Failure to do so results in a kernel fault. The entry point must then create a DPrimaryMediaBase object and register it with the local media subsystem. To support paging, the entry point needs altering to register the paging device with the demand paging subsystem and instantiate and install the factory object.

DECLARE_STANDARD_EXTENSION()
    {   
    TInt r=Kern::DfcQInit(&TestMediaDfcQ,KTestThreadPriority,&KTestMediaThreadName);
    if (r|=KErrNone)
        return r;
 
    DPrimaryMediaBase* pM=new DPrimaryMediaBase;
    if (!pM)
        return r;

    pM->iDfcQ=&TestMediaDfcQ;
    r=LocDrv::RegisterMediaDevice(
        MEDIA_DEVICE_NAND,
        NAND_DRIVECOUNT,
        NAND_DRIVELIST,
        pM,
        NAND_NUMMEDIA,
        NAND_DRIVENAME);
    if (r != KErrNone)
        return r;

    r = LocDrv::RegisterPagingDevice(
        pM,
        NAND_PAGEDRIVELIST,
        NAND_PAGEDRIVECOUNT,
        PAGING_TYPE,
        SECTOR_SHIFT,
        NUM_PAGES);
    if (r == KErrNone)
        {
        device = new DPhysicalDeviceMediaTest;
        if (device == NULL)
            return KErrNoMemory;
        r = Kern::InstallPhysicalDevice(device);
        }
    // Ignore error if demand paging not supported by kernel
    else if (r == KErrNotSupported)
        r = KErrNone;
    else
        return r;

    pM->iMsgQ.Receive();
    return KErrNone;
    }

The fifth parameter passed to the function LocDrv::RegisterPagingDevice() named SECTOR_SHIFT is the log2 of the sector size for the given media. For example, passing a value of 9 corresponds to a sector size of 512 for most media.

Note: The DECLARE_EXTENSION_PDD entry point is called some time later when the file server tries to load all the media drivers in the system. When this happens a second factory object is created by the media driver, but this is deleted by the kernel when it discovers that another factory object bearing the same name is already in its internal list.

Changes to DLocalDrive::ECaps handling

The TLocalDriveCaps structure needs to be modified so that:

Additionally, the TLocalDriveCaps ::iDriveAtt must have the KDriveAttLocal and KDriveAttInternal bits set and the KDriveAttRemovable bit cleared.

TInt DMediaDriverTest::Request(TLocDrvRequest& aRequest)
    {
    TInt r=KErrNotSupported;
    TInt id=aRequest.Id();

    if (id == DLocalDrive::ECaps)
        {
        TLocDrv* drive = aRequest.Drive();
        TLocalDriveCapsV4& c = *(TLocalDriveCapsV4*)aRequest.RemoteDes();   
        r=Caps(*drive,c);
        }
    // etc…
    }

TInt DMediaDriverTest::Caps(TLocDrv& aDrive, TLocalDriveCapsV4& caps)
    {
    // fill in rest of caps structure as usual…

    if(aDrive.iPrimaryMedia->iPagingMedia)
        caps.iMediaAtt|=KMediaAttPageable;
    if(aDrive.iPagingDrv)
        caps.iDriveAtt|=KDriveAttPageable; 
    }

Handling page requests

Four new request types need to be handled to support paging:

  • EWriteRequestFragment marks the start and middle of a sequence of writes.

  • Each sequence is terminated by a EWriteRequestFragmentLast request as long as none of the prior requests completed with an error.

  • ERomPageInRequest is treated as a normal read except that:

    1. the list of partitions reported by DMediaDriver::PartitionInfo does not normally include the partition containing the ROM image. Therefore, the local media subsystem does not know the absolute position from the start of the media of a particular ROM page. The position stored in ERomPageInRequest is offset from the start of the ROM image, rather than the start of the media. Therefore, the media driver must add the offset of the start of the ROM image to the position stored in ERomPageInRequest to obtain the absolute position before issuing a read request.

    2. when the read is complete the media driver needs to call TLocDrvRequest::WriteToPageHandler to write the data back to the client, rather than TLocDrvRequest::WriteRemote as for a normal read,

  • ECodePageInRequest is treated as a normal read, except that the function TLocDrvRequest::WriteToPageHandler should be used to write the data back to the demand paging subsystem. However, the position in the request is offset from the start of the media as for a normal read.

These request types are enumerated in the DMediaPagingDevice class:

NONSHARABLE_CLASS(DMediaPagingDevice) : public DPagingDevice
    {
public:
    enum TPagingRequestId
        {
        /** 
        Identifies any middle fragment of a Write request on a partition of a media that supports paging.
        */
        EWriteRequestFragment = 
            DLocalDrive::EFirstReqNumberReservedForPaging,

        /** 
        Identifies the last fragment of a Write request on a partition of a media that supports paging.
        */
        EWriteRequestFragmentLast = 
            DLocalDrive::EFirstReqNumberReservedForPaging+1,

        /** 
        Request for paging in (read) data from the ROM store area.
        */
        ERomPageInRequest =
            DLocalDrive::EFirstReqNumberReservedForPaging+2,

        /** 
        Request for paging in (read) data from the code store area.
        */
        ECodePageInRequest =
            DLocalDrive::ELastReqNumberReservedForPaging
        };
        //etc…
    }

Handling fragmented write requests

In many respects, EWriteRequestFragment and EWriteRequestFragmentLast can be treated as normal write requests. It should be noted however, that these write requests can be interleaved with requests from other file server threads if the media supports more than one partition, the resulting operations may be perceived as a functional break in behaviour.

If it is important to maintain backwards compatibility and to prevent write requests from being interleaved, the media driver must keep track of the current write-request chain and defer requests from other drive threads while a write-fragment chain is in progress by:

  • ensuring the local media subsystem LDD has been built with the __ALLOW_CONCURRENT_FRAGMENTATION__ macro undefined. This ensures that the local media subsystem never issues more than one write fragment at a time

  • modifying the paging-media driver so that it keeps track of write-request chains and defers any read or format requests received after the first fragment and before the last in a sequence. When undefined, the macro subsystem does not issue more than one write-request chain at a time.

Note: Write fragments should never be deferred, only read or format requests may be deferred.

To achieve this the media driver can maintain a bit mask, each bit of which represents a write in progress flag for a particular drive:

iFragmenting|=(0x1<<iCurrentReq->Drive()->iDriveNumber);

If a read or format request is received while any of the bits in iFragmenting are set, that request may be deferred.