Migration Tutorial: Direct Memory Addressing

To handle direct memory addressing, you must to make the following changes to your code.

If a media driver uses a data transfer mechanism like DMA, data transfer can be faster if the media driver knows that an address passed in an I/O request is a physical address. This is known as direct memory addressing.

Changes to registration

If the media driver code can handle physical addresses, it must tell the local media subsystem. This process is called registration. The media driver calls LocDrv::RegisterDmaDevice() to register as part of the media driver initialisation process. The media driver calls RegisterDmaDevice() after the media driver has registered with the local media subsystem.

After the call to LocDrv::RegisterDmaDevice(), the local media subsystem will test if the address in an I/O request is a physical address or a virtual address. The local media subsystem extracts the information that the media driver requires. The media driver gets this information through calls to the functions:

A TLocDrvRequest object represents an I/O request and is passed to the media driver.

There are three pieces of information that the local media subsystem needs from the media driver when the media driver registers:

  • The minimum number of bytes that the media device can transfer. For example, the architecture of some memory card types requires data transfer in blocks of 512 bytes. The local media subsystem can not support I/O requests for physical addresses where the length of the data is smaller than this minimum number. This limit prevents accidental access of the data outside the limits of the request.

  • The maximum number of bytes that the media driver can transfer in one burst. This value depends on the hardware. For eaxample,DMA Framework has limits that depend on the DMA controller.

  • The alignment of memory that the DMA controller requires. For example: a DMA controller might require 2 byte (word or 16 bit) alignment or 4 byte (double word or 32 bit) alignment. For 2 byte alignment, specify 2; for 4 byte alignment specify 4 etc. The local media subsystem can not support I/O requests for physical addresses that are not aligned according to this value.

You get all of this information from the documentation for the platform.

This example code extends the code shown in the section Media driver initialisation before the system is initialised. It adds a call to LocDrv::RegisterDmaDevice(). This call follows registration of the media driver with the local media subsystem.

DECLARE_STANDARD_EXTENSION()
    {
    TInt r=KErrNoMemory;
    DPrimaryMediaBase* pM=new DPrimaryMediaBase;
    if (pM)
        {//…Required here for Asynchronous creation (if supported)
        pM->iDfcQ = &MyDfcQ;
 
        // Perform registration here
        r = LocDrv::RegisterMediaDevice(MEDIA_DEVICE_TYPE,
                                        MEDIA_DRIVECOUNT,
                                        &IMediaDriveNumbers[0],
                                        pM,MEDIA_NUMMEDIA,KMediaDriveName
                                       );
        if ® != KErrNone)
            {
            return r;
            }
       
        // Register support for physical addressing.
        //
        // Note : in practice the values passed to RegisterDmaDevice() would be
        // either symbolic constants or functions that return values. If the 
        // media driver is split into platform independent and platform dependent
        // layers, and this code is in the independent layer, then you will need 
        // functions in the dependent layer to provide these values.
        r = LocDrv::RegisterDmaDevice(pM,
                                                    512,  // Block Addressing 512 Bytes
                                                    1024, // 1024 Byte address range
                                                    2 );  // 2 Byte alignment
        if ® != KErrNone)
            {
            return r;
            }
        ...
        }
    return®);
    }
      

Changes to request handling

To use physical addreses, you need to make changes to the code that deals with ERead and EWrite requests in your implementation of the DMediaDriver::Request() function. This section discusses the issues with ERead requests, but the principles apply to EWrite requests.

There are a number of points to consider:

Check if the address is physical

Call TLocDrvRequest::IsPhysicalAddress() to test if the address passed is a physical address. For example, for a ERead request:

...
   // iReadReq points to a TLocDrvRequest object
   ...
   iMediaStartPos = iReadReq->Pos();
   iTotalLength   = I64LOW(iReadReq->Length()); 
   iDoPhysicalAddress = iReadReq->IsPhysicalAddress();
   if(iDoPhysicalAddress)
      {
      ..< Physical address memory code >..
      }
   else
      {
      ...< Virtual address memory code >...

Physical address code

If you want to use the physical address, you need to get the physical address and the length of contiguous memory at this location. Call TLocDrvRequest::GetNextPhysicalAddress() to get the physical address and the length of physically contiguous memory. The length of physically contiguous memory can be smaller than the length supplied in the read request, because physical memory can be fragmented into a number of small blocks. If the length is smaller than the length supplied in the read or write request, you call TLocDrvRequest::GetNextPhysicalAddress() again to get the address of the next physically contiguous block of memory. You repeat this operation until the read request is complete. For example, for a ERead request:

...
   // iReadReq points to a TLocDrvRequest object
   ...
   TPhysAddr physAddr; 
   TInt physLength;
   TInt err = KErrNone;                
   err = iReadReq->GetNextPhysicalAddress(physAddr, physLength);
   if(err == KErrNone)
      {                       
      if (physLength < iTotalLength)
         {
         // Memory is fragmented, note remainder. You will need 
         // to use this code again using the remainder value.
         iRemaining    = iTotalLength – physLength;
         iTotalLength -= physLength;
         }
      
      // Start data transfer into the current physically
      // contiguous block of physical memory.
      DoDataTransfer(iMediaStartPos, physLength, physAddr);
      ...
      }

If you do not want to deal with fragmented physical memory, you can use your original code.

Virtual to physical address translation

Your code must not perform a virtual to physical address translation when it deals with physical memory. For example:

void DMMCRxDmaHelp::ChannelTransfer(const SDmaPseudoDes& aDes)
      {
      …
      TPhysAddr dest;

      if (iCurrentReq->IsPhysicalAddress())
         dest = (TPhysAddr) aDes.iDest;
      else
         dest = Epoc::LinearToPhysical(aDes.iDest);
      TPlatDma::SetDMARegister(KHoDMA_CDSA(iChno), dest);
      …
      }

Eliminate inter-process copy

You must change your code to remove calls to TLocDrvRequest::WriteRemote(). For ERead requests, data for transfer is already at the physical address. For example:

if (!iCurrentReq->IsPhysicalAddress())
      {
      if( (id == DMediaPagingDevice::ERomPageInRequest)||
          (id == DMediaPagingDevice::ECodePageInRequest) )
         {
         r = iCurrentReq->WriteToPageHandler((TUint8 *)(& iBufPtr [0]), len, usrOfst);
         }
      else if(id==DLocalDrive::ERead)
         {
         r = iCurrentReq->WriteRemote(&iBufPtr,usrOfst);
         }
      }

The same logic applies to EWrite requests. You need to remove calls to TLocDrvRequest::ReadRemote().

Test your changes

You are recommended to run regression tests on your changed code to makes sure that the media driver operates correctly.

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);