30 November 2008

Read CID and CSD C# implementation.

Ok, here goes. It's pretty huge, so I'll just post the important bits and post a download link to the source at the bottom. I've not yet started on getting overlap to work. YOU HAVE TO SET THE BUILD TARGET TO x86. (if you have an express edition then you'll need to expose the configuration manager first - its in the settings somewhere - "show advanced build options" or something).

(Remember this is Windows XP/Vista, Admin privileges required, build target must be x86, SD card must be attached via an SD reader connected directly to the PCI host - no USB reader...)

First up...

ENUMS - Boring they are in the header files in the WDK

public enum SdCommandClass : uint
    Standard,                       // SDCC_STANDARD
    AppCmd                          // SDCC_APP_CMD

public enum SdTransferDirection : uint
    Unspecified,                    // SDTD_UNSPECIFIED
    Read,                           // SDTD_READ
    Write                           // SDTD_WRITE

public enum SdTransferType : uint
    Unspecified,                    // SDTT_UNSPECIFIED
    CmdOnly,                        // SDTT_CMD_ONLY
    SingleBlock,                    // SDTT_SINGLE_BLOCK
    MultiBlock,                     // SDTT_MULTI_BLOCK
    MultiBlockNoCmd12               // SDTT_MULTI_BLOCK_NO_CMD12

public enum SdResponseType : uint
    Unspecified,                    // SDRT_UNSPECIFIED
    None,                           // SDRT_NONE
    R1,                             // SDRT_1
    R1b,                            // SDRT_1B
    R2,                             // SDRT_2
    R3,                             // SDRT_3
    R4,                             // SDRT_4
    R5,                             // SDRT_5
    R5b,                            // SDRT_5B
    R6                              // SDRT_6

public enum SffdiskDcmd : uint
    GetVersion,                     // SFFDISK_DC_GET_VERSION
    LockChannel,                    // SFFDISK_DC_LOCK_CHANNEL
    UnlockChannel,                  // SFFDISK_DC_UNLOCK_CHANNEL
    DeviceCommand                   // SFFDISK_DC_DEVICE_COMMAND
public enum IoCtlCode : uint
    SffdiskQueryDeviceProtocol = 0x71E80,       // IOCTL_SFFDISK_QUERY_DEVICE_PROTOCOL   
    SffdiskDeviceCommand = 0x79E84,             // IOCTL_SFFDISK_DEVICE_COMMAND   
    VolumeGetVolumeDiskExtents = 0x560000       // IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS

Native Methods - There are plenty of overrides of DeviceIOControl. The last one that takes byte[] sends the sd command. Note the use of SafeHandles instead of IntPtr.

[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
public extern static SafeFileHandle CreateFile(String fileName, AccessRights desiredAccess, ShareModes shareMode, IntPtr securityAttributes, CreationDisposition creationDisposition, int flagsAndAttributes, IntPtr hTemplateFile);

// overload used for querydeviceprotocol
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, IntPtr inBuffer, int inBufferSize, ref SffdiskQueryDeviceProtocolData outBuffer, int outBufferSize, out int bytesReturned, IntPtr overlapped);

// overload used for getting the disk extents
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, IntPtr inBuffer, int inBufferSize, out DiskExtents outBuffer, int outBufferSize, out int bytesReturned, IntPtr overlapped);

// overload used for getting more than 1 diskextent
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, IntPtr inBuffer,int inBufferSize, IntPtr outBuffer, int outBufferSize, out int bytesReturned, IntPtr overlapped);

// Overload for the CID
[DllImport("kernel32", SetLastError = true)]
public extern static bool DeviceIoControl(SafeFileHandle hVol, IoCtlCode controlCode, Byte[] inBuffer, int inBufferSize, Byte[] outBuffer, int outBufferSize, out int bytesReturned, IntPtr ovelapped);

The Structures - No fancy marshaling required. Again, see the WDK header files.

struct SdCmdDescriptor
    public Byte CommandCode;
    public SdCommandClass CmdClass;
    public SdTransferDirection TransferDirection;
    public SdTransferType TransferType;
    public SdResponseType ResponseType;
    public int GetSize()
        return Marshal.SizeOf(this);
struct SffdiskDeviceCommandData
    public ushort Size;                     // 0
    public ushort Reserved;                 // 2
    public SffdiskDcmd Command;             // 4
    public ushort ProtocolArgumentSize;     // 8
    public uint DeviceDataBufferSize;       // 12
    public uint Information;                // 16   *ULONG_PTR*, Data[] Follows.
    public void Init()
        this.Size = (ushort)Marshal.SizeOf(this);
struct SffdiskQueryDeviceProtocolData
    public ushort Size;
    public ushort Reserved;
    public Guid ProtocolGuid;
    public void Init()
        this.Size = (ushort)Marshal.SizeOf(this);

The class that makes the call - could do with more tidying up. CID and CSD are classes with lots of properties, the constructor takes the byte array returned by the deviceIOControl call and fills in the properties. They probably have mistakes too. See the linked code.

using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace JDMcF.SDCard
    // This class might get some information about an SDCard.
    // Does not work with USB SD Card Readers.
    // Does not work with some SD Bus Host Drivers.
    // Administrator privileges required.
    // I don't know if the IOCTLs work with mobile devices.
    // I don't know if I'm translating the bytes from the CID correctly.
    public class SDCard
        private static readonly Guid GuidSffProtocolSd = new Guid("AD7536A8-D055-4C40-AA4D-96312DDB6B38");

        private CID cid;
        public CID CID { get { return cid; } }

        private CSD csd;
        public CSD CSD { get { return csd; } }

        private DriveInfo driveInfo;
        public DriveInfo DriveInfo { get { return driveInfo; } }

        private string physicalDrivePath;
        public string PhysicalDrivePath { get { return physicalDrivePath; } }

        private SDCard(string physicalDrivePath, DriveInfo driveInfo)
            // At first only these are initialised. It takes a second or so to
            // read the CID so we so
            // it on another thread and raise an event once done.
            this.physicalDrivePath = physicalDrivePath;
            this.driveInfo = driveInfo;

        public static List<SDCard> GetSDCards()
            List<SDCard> cards = new List<SDCard>();
            // There are probably ways to mount the card elsewhere,
            // so it might miss those...
            foreach (DriveInfo di in System.IO.DriveInfo.GetDrives())
                // Are all SD Cards Removable? I don't know!
                if (di.DriveType == DriveType.Removable)
                    // We are enumerating volumes. Volumes can span physical
                    // disks. Work out how many physical disks are involved
                    // with each volume. (seems unlikely, but I just figured
                    // out how to do this so what the heck)...
                    List<string> physicalPaths =
                    foreach (string physicalPath in physicalPaths)
                        if (IsSD(physicalPath))
                            cards.Add(new SDCard(physicalPath, di));
            return cards;

        // Send IOCTL_SFFDISK_QUERY_DEVICE_PROTOCOL to see if the handle
        // belongs to an SD Card.
        private static bool IsSD(string physicalPath)
            SafeFileHandle hVol = null;
                hVol = NativeMethods.CreateFile(physicalPath, AccessRights.GenericRead, ShareModes.FileShareRead  ShareModes.FileShareWrite,
IntPtr.Zero, CreationDisposition.OpenExisting, 0, IntPtr.Zero);
                if (hVol.IsInvalid)
                    throw new Win32Exception("Couldn't CreateFile for " +
                SffdiskQueryDeviceProtocolData queryData1 =
                    new SffdiskQueryDeviceProtocolData();
                int bytesReturned;
                bool result = NativeMethods.DeviceIoControl(hVol, IoCtlCode.SffdiskQueryDeviceProtocol, IntPtr.Zero, 0, ref queryData1, queryData1.Size, out bytesReturned, IntPtr.Zero);
                return queryData1.ProtocolGuid.Equals(GuidSffProtocolSd);
                if (hVol != null)
                    if (!hVol.IsInvalid)

        public void RefreshData()

        // Send the command in the array. On return the array will have any
        // response.
        private void SendCommand(byte[] command)
            SafeFileHandle hVol = null;
                hVol = NativeMethods.CreateFile(PhysicalDrivePath, AccessRights.GenericRead  AccessRights.GenericWrite, ShareModes.FileShareRead  ShareModes.FileShareWrite, IntPtr.Zero, CreationDisposition.OpenExisting, 0, IntPtr.Zero);
                int bytesReturned;
                bool result = NativeMethods.DeviceIoControl(hVol, IoCtlCode.SffdiskDeviceCommand, command, command.Length, command, command.Length, out bytesReturned, IntPtr.Zero);
                if (!result) throw new Win32Exception();
                if (hVol != null)
                    if (!hVol.IsInvalid)

        // Get the CID or CSD. They are almost identical commands...
        private void GetRegister(Register register)
            byte[] command = null;
            SffdiskDeviceCommandData commandData = new SffdiskDeviceCommandData();
            SdCmdDescriptor commandDescriptor = new SdCmdDescriptor();
            commandData.Command = SffdiskDcmd.DeviceCommand;
            commandData.ProtocolArgumentSize = (ushort)commandDescriptor.GetSize();
            commandData.DeviceDataBufferSize = 16;
            commandDescriptor.CommandCode = (byte)register;   // <--- Not what the documentation indicates!
            commandDescriptor.CmdClass = SdCommandClass.Standard;
            commandDescriptor.TransferDirection = SdTransferDirection.Read;
            commandDescriptor.TransferType = SdTransferType.CmdOnly;
            commandDescriptor.ResponseType = SdResponseType.R2;

            // Now get the structs into the byte[]
            command = new byte[commandData.Size + commandData.ProtocolArgumentSize + commandData.DeviceDataBufferSize];
            IntPtr hBuf = Marshal.AllocHGlobal(command.Length);
            Marshal.StructureToPtr(commandData, hBuf, true);
            IntPtr descriptorOffset = new IntPtr(hBuf.ToInt32() + commandData.Size);
            Marshal.StructureToPtr(commandDescriptor, descriptorOffset, true);
            Marshal.Copy(hBuf, command, 0, command.Length);

            // Strip out the return bytes that live at the end of the command byte array.
            byte[] regBytes = new byte[16];
            Buffer.BlockCopy(command, command.Length - 16, regBytes, 0, 16);

            if (register == Register.CID)
                cid = new CID(regBytes);
                csd = new CSD(regBytes);

Formatting is awful...

27 November 2008

Howto: Get the physical drive string //./PhysicalDriveX from a path

There are lots of strings that you can feed CreateFile, if we are looking at volumes and drives then they include:

The Unique Volume Name:


You can list those at the command prompt with: "mountvol".

The mount point:


If you really want to list them at the command line, then "fsutil fsinfo drives" will do the trick.

The physical drive string (or whatever it is called)


command: "wmic diskdrive get name,size,model"

But, how can we get all of the physical drive strings available? Well, the hint is in the CreateFile documentation.

To obtain the physical drive for a volume, open a handle to the volume and call the DeviceIoControl function with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS. This control code returns the disk number of offset for each of the volume's extents; a volume can span disks.

In .Net then, start by enumerating all the drives, and create strings like the mount points above "\\.\X:". Use this with CreateFile to get a handle to the volume. Then call DeviceIOControl with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS.

The Class that does the hard work:

Project type - vb.net class library (2005 or 2008) - name JdMcF.VolumeInfo:

Option Strict On
Option Explicit On

Imports Microsoft.Win32.SafeHandles
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.ComponentModel

Public Class VolumeInfo

   Private Const GenericRead As Integer = &H80000000
   Private Const FileShareRead As Integer = 1
   Private Const Filesharewrite As Integer = 2
   Private Const OpenExisting As Integer = 3
   Private Const IoctlVolumeGetVolumeDiskExtents As Integer = &H560000
   Private Const IncorrectFunction As Integer = 1
   Private Const ErrorInsufficientBuffer As Integer = 122

   Private Class NativeMethods
       <DllImport("kernel32", CharSet:=CharSet.Unicode, SetLastError:=True)> _
       Public Shared Function CreateFile( _
           ByVal fileName As String, _
           ByVal desiredAccess As Integer, _
           ByVal shareMode As Integer, _
           ByVal securityAttributes As IntPtr, _
           ByVal creationDisposition As Integer, _
           ByVal flagsAndAttributes As Integer, _
           ByVal hTemplateFile As IntPtr) As SafeFileHandle
       End Function

       <DllImport("kernel32", SetLastError:=True)> _
       Public Shared Function DeviceIoControl( _
           ByVal hVol As SafeFileHandle, _
           ByVal controlCode As Integer, _
           ByVal inBuffer As IntPtr, _
           ByVal inBufferSize As Integer, _
           ByRef outBuffer As DiskExtents, _
           ByVal outBufferSize As Integer, _
           ByRef bytesReturned As Integer, _
           ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
       End Function

       <DllImport("kernel32", SetLastError:=True)> _
       Public Shared Function DeviceIoControl( _
           ByVal hVol As SafeFileHandle, _
           ByVal controlCode As Integer, _
           ByVal inBuffer As IntPtr, _
           ByVal inBufferSize As Integer, _
           ByVal outBuffer As IntPtr, _
           ByVal outBufferSize As Integer, _
           ByRef bytesReturned As Integer, _
           ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
       End Function
   End Class

   ' DISK_EXTENT in the msdn.
   <StructLayout(LayoutKind.Sequential)> _
   Private Structure DiskExtent
       Public DiskNumber As Integer
       Public StartingOffset As Long
       Public ExtentLength As Long
   End Structure

   <StructLayout(LayoutKind.Sequential)> _
   Private Structure DiskExtents
       Public numberOfExtents As Integer
       Public first As DiskExtent ' We can't marhsal an array if we don't know its size.
   End Structure

   ' A Volume could be on many physical drives.
   ' Returns a list of string containing each physical drive the volume uses.
   ' For CD Drives with no disc in it will return an empty list.
   Public Shared Function GetPhysicalDriveStrings(ByVal driveInfo As DriveInfo) As List(Of String)
       Dim sfh As SafeFileHandle = Nothing
       Dim physicalDrives As New List(Of String)(1)
       Dim path As String = "\\.\" & driveInfo.RootDirectory.ToString.TrimEnd("\"c)
           sfh = NativeMethods.CreateFile(path, GenericRead, FileShareRead Or Filesharewrite, IntPtr.Zero, _
                                                          OpenExisting, 0, IntPtr.Zero)
           Dim bytesReturned As Integer
           Dim de1 As DiskExtents = Nothing
           Dim numDiskExtents As Integer = 0
           Dim result As Boolean = NativeMethods.DeviceIoControl(sfh, IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, _
                                                                 0, de1, Marshal.SizeOf(de1), bytesReturned, IntPtr.Zero)
           If result = True Then
               ' there was only one disk extent. So the volume lies on 1 physical drive.
               physicalDrives.Add("\\.\PhysicalDrive" & de1.first.DiskNumber.ToString)
               Return physicalDrives
           End If
           If Marshal.GetLastWin32Error = IncorrectFunction Then
               ' The drive is removable and removed, like a CDRom with nothing in it.
               Return physicalDrives
           End If
           If Marshal.GetLastWin32Error <> ErrorInsufficientBuffer Then
               Throw New Win32Exception
           End If           
           ' Houston, we have a spanner. The volume is on multiple disks.
           ' Untested...
           ' We need a blob of memory for the DISK_EXTENTS structure, and all the DISK_EXTENTS
           Dim blobSize As Integer = Marshal.SizeOf(GetType(DiskExtents)) + _
                                     (de1.numberOfExtents - 1) * Marshal.SizeOf(GetType(DiskExtent))
           Dim pBlob As IntPtr = Marshal.AllocHGlobal(blobSize)
           result = NativeMethods.DeviceIoControl(sfh, IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, 0, pBlob, _
                                                  blobSize, bytesReturned, IntPtr.Zero)
           If result = False Then Throw New Win32Exception
           ' Read them out one at a time.
           Dim pNext As New IntPtr(pBlob.ToInt32 + 4) ' is this always ok on 64 bit OSes? ToInt64?
           For i As Integer = 0 To de1.numberOfExtents - 1
               Dim diskExtentN As DiskExtent = DirectCast(Marshal.PtrToStructure(pNext, GetType(DiskExtent)), DiskExtent)
               physicalDrives.Add("\\.\PhysicalDrive" & diskExtentN.DiskNumber.ToString)
               pNext = New IntPtr(pNext.ToInt32 + Marshal.SizeOf(GetType(DiskExtent)))
           Return physicalDrives
           If sfh IsNot Nothing Then
               If sfh.IsInvalid = False Then
               End If
           End If
       End Try
   End Function

End Class

The Test project:

Project type: vb.net windows forms app (2005 or 2008) name: whatever.

Option Strict On
Option Explicit On
Option Infer Off

Imports JDMcF.VolumeInfo
Imports System.IO
Imports System.Text

Public Class Form1

   Private lv1 As New ListView

   Sub New()

       ' This call is required by the Windows Form Designer.

       ' Add any initialization after the InitializeComponent() call.
       With lv1
           .View = View.Details
           .Columns.Add("Root", 100, HorizontalAlignment.Left)
           .Columns.Add("Physical Drive String", 200, HorizontalAlignment.Left)
           .Dock = DockStyle.Fill
       End With
   End Sub

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       For Each di As DriveInfo In DriveInfo.GetDrives
           Dim drivesList As List(Of String) = VolumeInfo.GetPhysicalDriveStrings(di)

           Dim drives As New StringBuilder
           If drivesList.Count > 0 Then
               For Each s As String In drivesList
                   drives.Append(", ")
               drives.Remove(drives.Length - 2, 2)
           End If
           Dim lvi As New ListViewItem(di.RootDirectory.ToString)
   End Sub

End Class

Linky to code:

26 November 2008

SD Card - Read CID/Serial number - Musings

Well I discovered that my .Net app does actually work on 32 bit operating systems too. Assuming I'm reading the data correctly, I can read CIDs in XP SP2, on an old Asus W5 laptop. It also has an integral card reader. BUT: it didn't work at first. DeviceIOControl returned "Invalid Function" (Win32 Error code 1). I went in to device manager and changed the driver over:




So, It looks like the driver plays an important role too...

So to read the CID, so far I've found:

1) You can't get it if the reader is a USB device. 2) You can't get it if the driver doesn't like the DeviceIOControl call.

CreateFile path for the Volume:

There are 2 choices mentioned on forums/in documentation. "//./X:" or "//./PhysicalDriveX". There's another one with a guid too.

I've been using "//./X:" as it worked. Maybe using the other path to get the handle will make a difference on the machine where I can't get it to work:

C:\Users\SEP>wmic diskdrive get name,size,model Model Name Size FUJITSU MHW2160BJ G2 \\.\PHYSICALDRIVE0 160039272960 SD Memory Card \\.\PHYSICALDRIVE1 8225280

Edit: I'm now on about version 5 of my program and I'm using C#! So, I've moved from C++/CLI -> VB.Net -> C#... It now works on the machine I mentioned above, and I'm using the physicaldrive string. I'm trying to get Overlapped to work on x64 and x32, it seems there are some problems with NativeOverlapped. This will allow the call to read the CID and CSD to run on another thread, and the user to be notified when the results come back. (Reading the CID takes a noticable amount of time.) It requires a pointer, hence the move to C#.

16 November 2008

C++ Interop example. FlashWindowEx

I've been using C++ occasionally instead of P/Invoke. I'm not a c++ programmer.

Here's how to use c++ express 2008 and vb.net 2008 together to call a platform method.

  1. First start C++.
  2. File->New->Project-> Project Type = CLR and use the Class Library template, set the project name to Flasher.
  3. Insert some code into Stdafx.h so that it looks like this:

    #pragma once #define STRICT #define WIN32_LEAN_AND_MEAN #include <windows.h> using namespace System;

    You can see that we have included windows.h, which will give us access to many standard c++ types, such as BOOL. Windows.h is huge, so we set lean and mean too, which strips some infrequently used items -- they won't be included. #define STRICT is similar to Option Strict in vb.net. It stops you being lazy!

  4. Next we need to declare a class and a static method (which is a vb.net Shared method):

    // Flasher.h

    #pragma once using namespace System; namespace Flasher { public ref class Flasher { public: static void Flash(IntPtr); }; }
  5. Now we add method code in the cpp file:

    // This is the main DLL file.

    #include "stdafx.h" #include "Flasher.h" namespace Flasher { void Flasher::Flash(IntPtr hWndManaged) { FLASHWINFO info; ZeroMemory(&info, sizeof(FLASHWINFO)); info.uCount = 5; info.dwFlags = FLASHW_CAPTION; info.dwTimeout = 0; info.hwnd = (HWND) hWndManaged.ToPointer(); info.cbSize = sizeof(info); BOOL result = FlashWindowEx(&info); }


    First we include those header files, so we get all of the methods and types declared in windows.h, and our own Flash method. Next we have the Flash method. To call FlashWindowEx you send a FLASHWINFO structure. If we were going to P/Invoke this, then we would need to declare our own versions of FlashWindowEx and FLASHWINFO. Here we don't have to, as we have the header files included. At the same time, this is a .Net dll, so our Flash method will be usable from VB.Net. After declaring a FLASHWINFO variable, we zero the memory. This is because C++ doesn't do it for us, we could get all sorts of junk in the new structure's fields if we don't do this. The & in &info is telling ZeroMemory the address where the info structure lives. We tell it to flash 5 times. We tell it to flash the caption. We tell it to flash at the default rate (= the systems cursor blink rate) by setting timeout to 0. Next we need to convert the managed IntPtr that is the windows Handle, into the unmanaged HWND type. This involves the ToPointer method, and a cast. Finally we set the structure size and send it off.

  6. Does it build?

  7. No it doesn't. Unresolved token, and unresolved external symbol. There is a linker error, look at the msdn page for FlashWindowEx: Library: Use User32.lib This means that we need to link against User32.lib. We aren't, so we get the error. Right click the project name "Flasher" in the solution explorer. Click properties. Look at: Configuration Properties/Linker/Input/Additional Dependencies Make sure the additional dependencies line is selected and click the ellipsis (...). A quick glance makes you think user32 is linked, but actually the list is a list of thinks that can be inherited, but we have $(NoInherit) set. Add user32.lib to the listbox. Once done, click Ok, and you should see: Additional Dependencies: user32.lib $(NOINHERIT)

  8. Does it build?

  9. No it doesn't. This time it's my antivirus software keeping the file open, causing a file access error: "mt.exe : general error c101008d: Failed to write the updated manifest to the resource of file" One that's sorted, it builds. Add an antivirus exception for mt.exe. For me it lives here: (C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin)

  10. Start a new VB.Net project. Add a button. Click Project->Add Reference, select the browse tab, navigate to the C++ dll that was built and add that.

  11. Add the following to the button click code:


  12. Does it build? For me - yes!

  13. Does it run? For me - no! I'm using Vista 64, and get: "Could not load file or assembly 'Flasher, Version=1.0.3245.7081, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format." This is fixed by setting the Target for the VB.Net program to x86, it was on AnyCPU, the dll is win32! In vb express you might need to do: Tools->Options->Projects and Solutions->General->Show Advanced Build Configurations then, you will need: Build->Configuration Manager->Active Solution Platform -- select <New> and x86

  14. It finally works.

For a simple win32 api call this is too much work. It might be worth it for performance if you are calling something a lot. If you have a complicated api, then it pays off as you don't have to make managed versions of everything. If the method is in a static library then this is a good technique.

15 November 2008

Read secure digital (SD) card serial number from the CID

(Quick link to an executable, this might read the serial number on Windows XP/Vista/32/64 when you are not using a USB card reader (so when you have the card in a slot on the side of a laptop it should work - though it depends on the driver!)). Requires .Net Framework 3.5.

I got an email about a post from ages ago on the msdn forums about reading the serial number from an SD Card. People want to do this as the number is factory stamped and unchangeable, so it could be used to protect programs from being copied. Distribute your program on an SD Card, have it check the serial, and have it fail if the number doesn't match a hard-coded version. Maybe there's more to it than that. But anyway the thread went along the lines of:

1) I want to read the serial 2) Someone suggested: Well use DeviceIOControl to send IOCTL_DISK_GET_STORAGEID to the drive.

At the time I'd just been playing with DeviceIOControl, and had spent ages working out how to use it from .Net. There weren't any useful examples that I could find. I was working at trying to make a CD Burner, using COM Interop to talk to IMAPI on XP (before IMAPI2). I'd learnt about DeviceIOControl, and how it can be used to send messages straight to a device. I used it to find out things that IMAPI couldn't tell you - like the exact media in the drive, and whether the drive was ready. I eventually got a vb.net program that could burn MP3s to CD. I never quite finished it though. I wasn't sure how to deal with all the possible errors during burning - there were so many!

I quickly wrote some code that demonstrated sending the message. I didn't know if the number was really the hardware stamped serial or whether it was just something windows dreamt up (like the volume serial you get with dir c:\).

I was asked again this week about this, and pointed in the direction of the concise SD specification (you can only see the big one if you pay $$$$, as it isn't an open standard). It's call the simplified version of the physical layer spec. This describes various commands that can be sent. Command 10 returns the CID. The CID is the bit we are interested in, the Card IDentification register:

Name Field Width CID-slice
Manufacturer ID MID 8 [127:120]
OEM/Application ID OID 16 [119:104]
Product Name PNM 40 [103:64]
Product Revision PRV 8 [63:56]
Product Serial Number PSN 32 [55:24]
reserved -- 4 [23:20]
Manufacturing Date MDT 12 [19:8]
CRC7 Checksum CRC 7 [7:1]
not used, always 1 -- 1 [0:0]

There we have 128 bits used to identify the device. I was asked if the number that my earlier program grabbed was this number from the CID. I don't know...(edit - NO it isn't the same. IOCTL_DISK_GET_STORAGEID is useless - it gets a serial that Windows invents when it formats a volume.)

I decided to try and get this CID number out, and I've managed to, but there are provisos. My program (VB.Net) currently works only on Vista 64. Weirdly it only works when compiled for the x86 platform (the build target in visual studio). When I tried it on Vista 32 I got invalid handle errors. Sheesh. The other biggy is that it only works when the SD Card is plugged into a reader that is attached directly to the pci bus. If USB is involved in the chain, then the DeviceIOControl messages go to the USB host controller, and it doesn't understand them. It works on my laptop's reader, but not on a fan controller/card reading gadget plugged in to my desktop as its card reader is attached via a USB header on the mobo.

Here are some pics of the CIDs. They came out reversed, and the endianness of the serial number and manufacturer date were unhelpful. In the pics below, the 16 bytes are the ones I got in my buffer, but reversed order. They tally quite well with the above table. But, the leftmost byte (0 in each case) shouldn't be there, and we are missing the CRC and final bit. So it goes - Unspecified byte, MID, OID, OID, PNM, PNM, PNM, PNM, PNM, PRV, PSN, reserved+MDT, MDT. And the CRC is missing.


I bought the two Crucial 4GB cards at the same time, note the serial numbers are adrift by 2 - a good indication I'm not getting gobbledegook. The Manufacture date is mm/yyyy. Also the numbers tally with the hardware IDs found in Windows Device Manager, which are described in the DDK, er WDK rather. Which you can get for free now. I bought the Veho card two weeks ago, looks like it was made just last month. It also looks like Vehos are made by Corsair - or maybe they are both made by some other company. Crucial don't fill in the product name or version, all the bytes were 0.

The bit where is says "This is an SD card", is because it also checks if the drive selected in the combobox is an SD card before attempting to get data.

So, how's it done? Well, you send an IOCTL specifying an SD Command with DeviceIOControl. Again it's in the WDK. These are just user mode calls, so we aren't in the realms of writing a driver. The main difficulties were trying to decipher the documentation, which appears to be plain wrong in places. This lead to many many errors being returned by DeviceIOControl. Eventually I managed to send off command 0 without any complaint whatsoever, and figured I must be doing something right. I was using C++ code from elsewhere in the msdn forums and a bit of C++ Interop. I then got something back from cmd 10. I then went and did it in VB.Net instead as all this C++ was driving me mad. I can read it, but programming in it is a very laborious process for me.

I used IOCTL_SFFDISK_QUERY_DEVICE_PROTOCOL to check if a mount point was an SD card. Then IOCTL_SFFDISK_DEVICE_COMMAND to send cmd10. DeviceIOControl just takes the IOCTL code and a blob of memory - in .Net I used a byte array. The byte array contains a SFFDISK_DEVICE_COMMAND_DATA structure first, then the SDCMD_DESCRIPTOR. Both of these structures were 20 bytes big on x64 vista when using c++, so I made sure they were that size in .Net too. The Cmd field of SDCMD_DESCRIPTOR is very nasty. The documentation says to fill it with a value from the SD_COMMAND_CODE data type and lists two "allowed values". Well, both of those values give errors. Instead you should set the sd command code - i.e. 10 for command 10, 0 for command 0!!!!! After that structure I sent 16 bytes to receive the CID. And unfortunately I get 15 bytes of CID with a 00 in front and no CRC. Maybe I have some alignment problems with the structures. It might explain why my code isn't porting too.

I'll iron out the bugs and try and get it running on my dilapidated axim 50v too with the compact framework. I'm currently attempting to flash the rom on it to something newer. I nearly binned it last week in a grand tidy up. I'll have to download the academic version of Visual Studio, which is Pro, to do the windows mobile stuff. I've only got Standard (which was a MS freebie from the launch). Code to follow. (note there are other, newer, posts on this on the blog, and some other code)

13 November 2008

Dell Laptop - delivery tracking saying "call lock out"

"This means your order is now ready for delivery and you will receive a call shortly to advise you of delivery date and options available for your order."

That is an explanation of the strange "call lock out" stage you see when you use order tracking from Syncreon (Walsh Western merged into this company) to see where your new dell gizmo is. I emailed them to find out what it meant. Whilst waiting for a reply, I got the automated call asking me to confirm the delivery time, and as soon as I hung up, a new bit had appeared on the tracking page showing the delivery date/time I arranged...