Wednesday, November 10, 2004

If you follow by blog, you've probably noticed one of my side projects is moving toward a device driver with managed code.  Why?  Because most any sane person will tell you it's either not possible or a bad idea.  But hey, I always want to know the why behind those statements.  “It will be too slow.”  Well, how slow, exactly?  “It won't be deterministic.”  What if I don't need it to be, or what if I can get around that axiom?

Well as a new motivator I had someone ask about using the SPI bus on an Applied Data Systems device using C#.  It's a slow bus, and determinism isn't really necessary, so this would be a classic opportunity.  Add to that the fact they don't need to to be a tru driver running under the auspices of device.exe - simply controlling the bus from their app is fine.

So last night I sat down and hammered out a general purpose physical address accessor class, which I'll post below and add to the SDF.  Then today I did a quick implementation test with it just to see how it does.  I didn't do the SPI driver because 1. it would take a while and 2. they need to understand how it works and it will do them some good to write it.  What I did do is use the class to control a GPIO on the PXA255 which is connected to an LED on my board.  This provided a quick visual check that I was indeed able to set the GPIO register values.  It also allowed me to use a scope to see how fast it would go.

Well, as a test I put it in a tight loop toggling the LED state as fast as possible, just a while(true) {on, off } kind of thing, and I did the same logic in a pure C app.  The C app had pulse durations ~500ns, plus or minus a pretty large margin because I didn't fiddle with thread priorities or anything.  The C# app had pulse durations ~1us.  So you can look at it glass-half-empty and say wow, it's twice as slow as native code, or glass-half-full and say, wow, that's well below millisecond resolution and quite suitable for a lot of stuff.

I love managed code.

//==========================================================================================
//
// OpenNETCF.IO.PhysicalAddressPointer
// Copyright (c) 2004, OpenNETCF.org
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the OpenNETCF.org Shared Source License.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the OpenNETCF.org Shared Source License
// for more details.
//
// You should have received a copy of the OpenNETCF.org Shared Source License
// along with this library; if not, email
licensing@opennetcf.org to request a copy.
//
// If you wish to contact the OpenNETCF Advisory Board to discuss licensing, please
// email
licensing@opennetcf.org.
//
// For general enquiries, email
enquiries@opennetcf.org or visit our website at:
//
http://www.opennetcf.org
//
//==========================================================================================
using System;
using System.Runtime.InteropServices;

namespace OpenNETCF.IO
{
 /// <summary>
 /// This class is used to access memory mapped addresses
 /// !!! DANGER WILL ROBINSON !!  You can cause serious problems using this class without knowing what you're doing!
 /// We reiterate the statement in our license that OpenNETCF provides absolutely no warranty on this code and you use it at your own risk
 /// </summary>
 public class PhysicalAddressPointer
 {
  // use 4k pages
  private const uint PAGE_SIZE  = 0x1000;

  // consts from winnt.h
  private const uint MEM_RESERVE  = 0x2000;
  private const uint PAGE_NOACCESS = 0x0001;
  private const uint PAGE_READWRITE = 0x0004;
  private const uint PAGE_NOCACHE  = 0x200;
  private const uint PAGE_PHYSICAL = 0x400;
  private const uint MEM_RELEASE  = 0x8000;

  private IntPtr m_virtualAddress = IntPtr.Zero;
  private IntPtr m_addressPointer = IntPtr.Zero;

  /// <summary>
  /// An accessor class to a physical memory address.
  /// </summary>
  /// <param name="physicalAddress">Physical Address to map</param>
  /// <param name="size">Minimum size of the desired allocation</param>
  /// <remarks>The physical address does not need to be aligned as the PhysicalAddressPointer will handle alignment
  /// The size value will aligned to the next multiple of 4k internally, so the actual allocation may be larger than the requested value</remarks>
  public PhysicalAddressPointer(uint physicalAddress, uint size)
  {
   m_addressPointer = MapPhysicalAddress(physicalAddress, size);
  }

  ~PhysicalAddressPointer()
  {
   if(m_virtualAddress != IntPtr.Zero)
   {
    VirtualFree(m_virtualAddress, 0, MEM_RELEASE);
   }
  }

  /// <summary>
  /// Write an array of bytes to the mapped physical address
  /// </summary>
  /// <param name="bytes">data to write</param>
  public void WriteBytes(byte[] bytes)
  {
   Marshal.Copy(bytes, 0, m_addressPointer, bytes.Length);
  }

  /// <summary>
  /// Write a 32-bit value to the mapped address
  /// </summary>
  /// <param name="data">data to write</param>
  public void WriteInt32(int data)
  {
   Marshal.WriteInt32(m_addressPointer, data);
  }

  /// <summary>
  /// Write a 16-bit value to the mapped address
  /// </summary>
  /// <param name="data">data to write</param>
  public void WriteInt16(short data)
  {
   Marshal.WriteInt16(m_addressPointer, data);
  }

  /// <summary>
  /// Write an 8-bit value to the mapped address
  /// </summary>
  /// <param name="data">data to write</param>
  public void WriteByte(byte data)
  {
   Marshal.WriteByte(m_addressPointer, data);
  }

  /// <summary>
  /// Read a series of bytes from the mapped address
  /// </summary>
  /// <param name="length">number of bytes to read</param>
  /// <returns>read data</returns>
  public byte[] ReadBytes(int length)
  {
   byte[] bytes = new byte[length];
   Marshal.Copy(m_addressPointer, bytes, 0, length);
   return bytes;
  }

  /// <summary>
  /// Read a 32-bit value from the mapped address
  /// </summary>
  /// <returns>read value</returns>
  public int ReadInt32()
  {
   return Marshal.ReadInt32(m_addressPointer);
  }

  /// <summary>
  /// Read a 16-bit value from the mapped address
  /// </summary>
  /// <returns>read value</returns>
  public short ReadInt16()
  {
   return Marshal.ReadInt16(m_addressPointer);
  }

  /// <summary>
  /// Read an 8-bit value from the mapped address
  /// </summary>
  /// <returns>read value</returns>
  public byte ReadByte()
  {
   return Marshal.ReadByte(m_addressPointer);
  }

  IntPtr MapPhysicalAddress(uint physicalAddress, uint size)
  {
   uint alignedAddress = 0;
   uint offset   = 0;
   uint alignedSize  = 0;
   IntPtr returnAddress = IntPtr.Zero;
   
   
   // get a page aligned address
   alignedAddress = PageAlignAddress(physicalAddress);
   offset = physicalAddress - alignedAddress;

   // get a page aligned size
   alignedSize = RoundSizeToNextPage(size + offset);

   // reserve some virtual memory
   m_virtualAddress = VirtualAlloc(0, alignedSize, MEM_RESERVE, PAGE_NOACCESS);

   // sanity check
   if(m_virtualAddress == IntPtr.Zero)
   {
    // allocation failure!
    return IntPtr.Zero;
   }

   
   // Map physical memory to virtual memory
   if(VirtualCopy (m_virtualAddress, (IntPtr)(alignedAddress >> 8), alignedSize,
    PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL) == 0)
   {
    // copy failure!
    VirtualFree(m_virtualAddress, 0, MEM_RELEASE);

    return IntPtr.Zero;
   }

   // offset and return
   return new IntPtr(m_virtualAddress.ToInt32() + offset);
  }

  // simply aligns an address to a page boundary to prevent data aborts and fun stuff like that
  uint PageAlignAddress(uint addressToAlign)
  {
   return addressToAlign & ~(PAGE_SIZE -1);
  }

  // allocations must be made in page multiples. 
  // this method finds the next multiple given a desired size
  uint RoundSizeToNextPage(uint size)
  {
   return (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
  }

  // p/invoke declarations
  [DllImport("coredll.dll", EntryPoint="VirtualAlloc", SetLastError=true)]
  private static extern IntPtr VirtualAlloc(uint lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

  [DllImport("coredll.dll", EntryPoint="VirtualCopy", SetLastError=true)]
  private static extern int VirtualCopy(IntPtr lpvDest, IntPtr lpvSrc, uint cbSize, uint fdwProtect);

  [DllImport("coredll.dll", EntryPoint="VirtualFree", SetLastError=true)]
  private static extern int VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);
 }
}

 

11/10/2004 3:28:34 PM (Eastern Standard Time, UTC-05:00)  #    Comments [5]  |