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