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