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]  | 
12/23/2004 10:05:25 AM (Eastern Standard Time, UTC-05:00)
Now folks are going to actually try this. It keeps me up at night.

Mike.
8/11/2005 2:51:33 AM (Eastern Daylight Time, UTC-04:00)
It's very beautiful site. I like it. All of the best
8/12/2005 4:04:22 AM (Eastern Daylight Time, UTC-04:00)
Hello! I was interested in your article. It is rather instructive.
8/12/2005 4:07:29 AM (Eastern Daylight Time, UTC-04:00)
OK seems to work now with this information
11/23/2005 4:39:06 AM (Eastern Standard Time, UTC-05:00)
Hi, i have to send a few bytes to GPIO ports by using CF 1.0 and VB.NET. i am using a panel pc that has 8 digital ports on it and has Win CE 4.2 OS on it. I changed your code to vb.net but i can not make it work. Would you pls check the code and give me some advice? Thanx in advance
'-----------------------
Imports System
Imports System.Runtime.InteropServices

Public Class PhysicalAddressPointer
Private Const PAGE_SIZE As Int32 = 4096
Private Const MEM_RESERVE As Int32 = 8192
Private Const PAGE_NOACCESS As Int32 = 1
Private Const PAGE_READWRITE As Int32 = 4
Private Const PAGE_NOCACHE As Int32 = 512
Private Const PAGE_PHYSICAL As Int32 = 1024
Private Const MEM_RELEASE As Int32 = 32768
Private m_virtualAddress As IntPtr = IntPtr.Zero
Private m_addressPointer As IntPtr = IntPtr.Zero

Public Sub New(ByVal physicalAddress As Int32, ByVal size As Int32)
m_addressPointer = MapPhysicalAddress(physicalAddress, size)
End Sub

Protected Overrides Sub Finalize()
If Not IntPtr.op_Equality(m_virtualAddress, IntPtr.Zero) Then
VirtualFree(m_virtualAddress, 0, MEM_RELEASE)
End If
End Sub

Public Sub WriteBytes(ByVal bytes As Byte())
Marshal.Copy(bytes, 0, m_addressPointer, bytes.Length)
End Sub

Public Sub WriteInt32(ByVal data As Integer)
Marshal.WriteInt32(m_addressPointer, data)
End Sub

Public Sub WriteInt16(ByVal data As Short)
Marshal.WriteInt16(m_addressPointer, data)
End Sub

Public Sub WriteByte(ByVal data As Byte)
Marshal.WriteByte(m_addressPointer, data)
End Sub

Public Function ReadBytes(ByVal length As Integer) As Byte()
Dim bytes(length) As Byte
Marshal.Copy(m_addressPointer, bytes, 0, length)
Return bytes
End Function

Public Function ReadInt32() As Integer
Return Marshal.ReadInt32(m_addressPointer)
End Function

Public Function ReadInt16() As Short
Return Marshal.ReadInt16(m_addressPointer)
End Function

Public Function ReadByte() As Byte
Return Marshal.ReadByte(m_addressPointer)
End Function

Function MapPhysicalAddress(ByVal physicalAddress As Int32, ByVal size As Int32) As IntPtr
Dim alignedAddress As Int32 = 0
Dim offset As Int32 = 0
Dim alignedSize As Int32 = 0
Dim returnAddress As IntPtr = IntPtr.Zero
alignedAddress = PageAlignAddress(physicalAddress)
offset = physicalAddress - alignedAddress
alignedSize = RoundSizeToNextPage(size + offset)
m_virtualAddress = VirtualAlloc(0, alignedSize, MEM_RESERVE, PAGE_NOACCESS)
If IntPtr.op_Equality(m_virtualAddress, IntPtr.Zero) Then
Return IntPtr.Zero
End If

If VirtualCopy(m_virtualAddress, CType((alignedAddress > 8), Int32), alignedSize, PAGE_READWRITE Or PAGE_NOCACHE Or PAGE_PHYSICAL) = 0 Then
VirtualFree(m_virtualAddress, 0, MEM_RELEASE)
Return IntPtr.Zero
End If
Return New IntPtr(m_virtualAddress.ToInt32 + offset)
End Function

Function PageAlignAddress(ByVal addressToAlign As Int32) As Int32
Return addressToAlign And Not (PAGE_SIZE - 1)
End Function

Function RoundSizeToNextPage(ByVal size As Int32) As Int32
Return (size + PAGE_SIZE - 1) And Not (PAGE_SIZE - 1)
End Function

Declare Function VirtualAlloc Lib "Coredll.dll" Alias "VirtualAlloc" (ByRef lpAddress As Int32, ByVal dwSize As Int32, ByVal flAllocationType As Int32, ByVal flProtect As Int32) As IntPtr

Declare Function VirtualCopy Lib "Coredll.dll" Alias "VirtualCopy" (ByVal lpvDest As IntPtr, ByVal lpvSrc As Int32, ByVal cbSize As Int32, ByVal fdwProtect As Int32) As Integer

Public Declare Function VirtualFree Lib "Coredll.dll" Alias "VirtualFree" (ByRef lpAddress As IntPtr, ByVal dwSize As Int32, ByVal dwFreeType As Int32) As Int32

End Class
'-----------------------
And here is my way to use this class
'-----------------------
Dim myObject As PhysicalAddressPointer
Try
myObject.MapPhysicalAddress(864, 1)
myObject.WriteByte(&HC1)
Catch ex As Exception
MsgBox(ex.Message)
End Try
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):