Showing posts with label SD Card CSD. Show all posts
Showing posts with label SD Card CSD. Show all posts

22 December 2008

Read SD Card CID (serial number)

I just moved blogs, so here's an overview of the SD card CID stuff. I've had some success reading the CID from an SD Card:

Where:

1) The OS is XP or Vista 2) The device is not attached via any USB gizmo. It works only in a card reader attached directly to the PCI bus. (Can scsi pass through get around this? It's not an ATA or SCSI command...) 3) Admin privileges...

What not to do:

1) IOCTL_DISK_GET_STORAGEID 2) GetFileInformationByHandle

As both these return a number assigned when the volume is created. It will change next time the drive is formatted. What I did:

Use IOCTL_SFFDISK_DEVICE_COMMAND to send command 10 (see the SD spec).

Read Secure Digital (SD) Card Serial Number from CID

SD Card Musings

Read CID and CSD C# implementation

Pocket PC?

I don't think it works.

The Future?

IEEE 1667

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

// SD_COMMAND_CLASS
public enum SdCommandClass : uint
{
    Standard,                       // SDCC_STANDARD
    AppCmd                          // SDCC_APP_CMD
};

// SD_TRANSFER_DIRECTION
public enum SdTransferDirection : uint
{
    Unspecified,                    // SDTD_UNSPECIFIED
    Read,                           // SDTD_READ
    Write                           // SDTD_WRITE
};

// SD_TRANSFER_TYPE
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
};

// SD_RESPONSE_TYPE
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
};

// SFFDISK_DCMD
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.

// SDCMD_DESCRIPTOR
[StructLayout(LayoutKind.Sequential)]
struct SdCmdDescriptor
{
    public Byte CommandCode;
    public SdCommandClass CmdClass;
    public SdTransferDirection TransferDirection;
    public SdTransferType TransferType;
    public SdResponseType ResponseType;
    public int GetSize()
    {
        return Marshal.SizeOf(this);
    }
}
// SFFDISK_DEVICE_COMMAND_DATA
[StructLayout(LayoutKind.Sequential)]
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);
    }
} 
// SFFDISK_QUERY_DEVICE_PROTOCOL_DATA
[StructLayout(LayoutKind.Sequential)]
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
    {
        // GUID_SFF_PROTOCOL_SD
        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 =
                        VolumeInfo.GetPhysicalDriveStrings(di);
                    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;
            try
            {
                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 " +
                                             physicalPath);
                SffdiskQueryDeviceProtocolData queryData1 =
                    new SffdiskQueryDeviceProtocolData();
                queryData1.Init();
                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);
            }
            finally
            {
                if (hVol != null)
                {
                    if (!hVol.IsInvalid)
                    {
                        hVol.Close();
                    }
                    hVol.Dispose();
                }
            }
        }

        public void RefreshData()
        {
            GetRegister(Register.CID);
            GetRegister(Register.CSD);
        }

        // Send the command in the array. On return the array will have any
        // response.
        private void SendCommand(byte[] command)
        {
            SafeFileHandle hVol = null;
            try
            {
                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();
            }
            finally
            {
                if (hVol != null)
                {
                    if (!hVol.IsInvalid)
                    {
                        hVol.Close();
                    }
                    hVol.Dispose();
                }
            }
        }

        // 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.Init();
            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);
            Marshal.FreeHGlobal(hBuf);

            SendCommand(command);
            // 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);
            }
            else
            {
                csd = new CSD(regBytes);
            }
        }
    }
}

Formatting is awful...

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:

1

TO:

2

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#.