Showing posts with label SD Card. Show all posts
Showing posts with label SD Card. 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#.

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.

CIDS

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)