package sd import ( "bytes" "encoding/binary" "io" "strconv" "time" ) // For reference of CID/CSD structs see: // See https://github.com/arduino-libraries/SD/blob/1c56f58252553c7537f7baf62798cacc625aa543/src/utility/SdInfo.h#L110 // CardKind classifies an SD card by its capacity class and specification // version, as discovered during card initialization. type CardKind uint8 // isTimeout reports whether err is one of the package's timeout errors. func isTimeout(err error) bool { return err == errReadTimeout || err == errWriteTimeout || err == errBusyTimeout } const ( // card types TypeSD1 CardKind = 1 // Standard capacity V1 SD card TypeSD2 CardKind = 2 // Standard capacity V2 SD card TypeSDHC CardKind = 3 // High Capacity SD card ) // CID is the Card Identification register, a 128-bit (16-byte) read-only // register holding the card's identification information: manufacturer, // product name, serial number and manufacturing date, among other data. // It is programmed during card manufacture and cannot be changed. type CID struct { data [16]byte } // DecodeCID decodes a CID from the first 16 bytes of b. It returns an error // if b is too short or if the CRC7/always-1 fields are invalid. func DecodeCID(b []byte) (cid CID, _ error) { if len(b) < 16 { return CID{}, io.ErrShortBuffer } copy(cid.data[:], b) if !cid.IsValid() { return cid, errBadCSDCID } return cid, nil } // RawCopy returns a copy of the raw CID data. func (c *CID) RawCopy() [16]byte { return c.data } // ManufacturerID is an 8-bit binary number that identifies the card manufacturer. The MID number is controlled, defined, and allocated to a SD Memory Card manufacturer by the SD-3C, LLC. func (c *CID) ManufacturerID() uint8 { return c.data[0] } // OEMApplicationID A 2-character ASCII string that identifies the card OEM and/or the card contents (when used as a // distribution media either on ROM or FLASH cards). The OID number is controlled, defined, and allocated // to a SD Memory Card manufacturer by the SD-3C, LLC func (c *CID) OEMApplicationID() uint16 { return binary.BigEndian.Uint16(c.data[1:3]) } // ProductName returns the product name, an ASCII string of up to 5 characters. func (c *CID) ProductName() string { return string(upToNull(c.data[3:8])) } // ProductRevision is composed of two Binary Coded Decimal (BCD) digits, four bits each, representing // an "n.m" revision number. The "n" is the most significant nibble and "m" is the least significant nibble. // As an example, the PRV binary value field for product revision "6.2" will be: 0110 0010b func (c *CID) ProductRevision() (n, m uint8) { rev := c.data[8] return rev >> 4, rev & 0x0F } // ProductSerialNumber returns the product serial number, a 32-bit binary number. func (c *CID) ProductSerialNumber() uint32 { return binary.BigEndian.Uint32(c.data[9:13]) } // ManufacturingDate returns the manufacturing date of the card, // e.g. year=2023, month=4 for April 2023. func (c *CID) ManufacturingDate() (year uint16, month uint8) { date := binary.BigEndian.Uint16(c.data[13:15]) return (date >> 4) + 2000, uint8(date & 0x0F) } // CRC7 returns the CRC7 checksum for this CID. May be invalid. Use [IsValid] to check validity of CRC7+Always1 fields. func (c *CID) CRC7() uint8 { return c.data[15] >> 1 } // Always1 checks the presence of the Always 1 bit. Should return true for valid CIDs. func (c *CID) Always1() bool { return c.data[15]&1 != 0 } // IsValid checks if the CRC and always1 fields are expected values. func (c *CID) IsValid() bool { return c.Always1() && CRC7(c.data[:15]) == c.CRC7() } // CSD is the Card Specific Data register, a 128-bit (16-byte) register that defines how // the SD card standard communicates with the memory field or register. This type is // shared among V1 and V2 type devices. type CSD struct { data [16]byte } // CSDv1 is the Card Specific Data register for V1 devices. See [CSD] for more info. type CSDv1 struct { CSD } // CSDv2 is the Card Specific Data register for V2 devices. See [CSD] for more info. type CSDv2 struct { CSD } // DecodeCSD decodes the CSD from a 16-byte slice. func DecodeCSD(b []byte) (CSD, error) { if len(b) < 16 { return CSD{}, io.ErrShortBuffer } csd := CSD{} copy(csd.data[:], b) if !csd.IsValid() { return csd, errBadCSDCID } return csd, nil } // csdStructure returns the version of the CSD structure. func (c *CSD) csdStructure() uint8 { return c.data[0] >> 6 } // Version returns the version of the CSD structure. Effectively returns 1+CSDStructure. func (c *CSD) Version() uint8 { return 1 + c.csdStructure() } // MustV1 returns the CSD as a CSDv1. Panics if the CSD is not version 1.0. func (c CSD) MustV1() CSDv1 { if c.csdStructure() != 0 { panic("CSD is not version 1.0") } return CSDv1{CSD: c} } // MustV2 returns the CSD as a CSDv2. Panics if the CSD is not version 2.0. func (c CSD) MustV2() CSDv2 { if c.csdStructure() != 1 { panic("CSD is not version 2.0") } return CSDv2{CSD: c} } // RawCopy returns a copy of the raw CSD data. func (c *CSD) RawCopy() [16]byte { return c.data } // TAAC returns the Time Access Attribute Class (data read access-time-1). func (c *CSD) TAAC() TAAC { return TAAC(c.data[1]) } // NSAC returns the Data Read Access-time 2 in CLK cycles (NSAC*100). func (c *CSD) NSAC() NSAC { return NSAC(c.data[2]) } // TransferSpeed returns the Max Data Transfer Rate. Either 0x32 or 0x5A. func (c *CSD) TransferSpeed() TransferSpeed { return TransferSpeed(c.data[3]) } // CommandClasses returns the supported Card Command Classes as a bitfield; // bit position i set means command class i is supported by the card. func (c *CSD) CommandClasses() CommandClasses { return CommandClasses(uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4) } // ReadBlockLen returns the Max Read Data Block Length in bytes. func (c *CSD) ReadBlockLen() int { return 1 << c.ReadBlockLenShift() } // ReadBlockLenShift returns the base-2 logarithm of [CSD.ReadBlockLen] (READ_BL_LEN field). func (c *CSD) ReadBlockLenShift() uint8 { return c.data[5] & 0x0F } // AllowsReadBlockPartial indicates that partial block reads (down to a // single byte) are allowed. Always true for SD cards. func (c *CSD) AllowsReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 } // AllowsWriteBlockMisalignment defines if the data block to be written by one command // can be spread over more than one physical block of the memory device. func (c *CSD) AllowsWriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 } // AllowsReadBlockMisalignment defines if the data block to be read by one command // can be spread over more than one physical block of the memory device. func (c *CSD) AllowsReadBlockMisalignment() bool { return c.data[6]&(1<<5) != 0 } // CRC7 returns the CRC read for this CSD. May be invalid. Use [IsValid] to check validity of CRC7+Always1 fields. func (c *CSD) CRC7() uint8 { return c.data[15] >> 1 } // Always1 checks the Always 1 bit. Should always evaluate to true for valid CSDs. func (c *CSD) Always1() bool { return c.data[15]&1 != 0 } // IsValid checks if the CRC and always1 fields are expected values. func (c *CSD) IsValid() bool { // Compare last byte with CRC and also the always1 bit. return c.Always1() && CRC7(c.data[:15]) == c.CRC7() } // ImplementsDSR defines if the configurable driver stage is integrated on the card. func (c *CSD) ImplementsDSR() bool { return c.data[6]&(1<<4) != 0 } // EraseSectorSizeInBytes returns how much memory is erased by a single // erase command, in bytes (SectorSize multiplied by the write block length). func (c *CSDv1) EraseSectorSizeInBytes() int64 { blklen := c.WriteBlockLen() numblocks := c.SectorSize() return int64(numblocks) * blklen } // SectorSize returns the size of an erasable sector in units of write blocks // (SECTOR_SIZE field, range 1..128). Its meaning varies with the CSD version: // for V1 it is the erase unit when [CSD.EraseBlockEnabled] is false; for V2 // it is fixed to 64KiB and does not reflect the real erase unit. func (c *CSD) SectorSize() uint8 { return 1 + ((c.data[10]&0b11_1111)<<1 | (c.data[11] >> 7)) } // EraseBlockEnabled defines granularity of unit size of data to be erased. // If enabled the erase operation can erase either one or multiple units of 512 bytes. func (c *CSD) EraseBlockEnabled() bool { return (c.data[10]>>6)&1 != 0 } // ReadToWriteFactor returns the typical write time as a power-of-2 multiple of // the read access time (R2W_FACTOR field), i.e. writeTime = readTime << factor. func (c *CSD) ReadToWriteFactor() uint8 { return (c.data[12] >> 2) & 0b111 } // WriteProtectGroupSizeInSectors indicates the size of a write protected // group in multiple of erasable sectors. func (c *CSD) WriteProtectGroupSizeInSectors() uint8 { return 1 + (c.data[11] & 0b111_1111) } // WriteBlockLen represents maximum write data block length in bytes. func (c *CSD) WriteBlockLen() int64 { return 1 << ((c.data[12]&0b11)<<2 | (c.data[13] >> 6)) } // WriteGroupEnabled indicates if write group protection is available. func (c *CSD) WriteGroupEnabled() bool { return c.data[12]&(1<<7) != 0 } // AllowsWritePartial Defines whether partial block sizes can be used in write block sizes. func (c *CSD) AllowsWritePartial() bool { return c.data[13]&(1<<5) != 0 } // FileFormat returns the file format on the card. This field is read-only for ROM. func (c *CSD) FileFormat() FileFormat { return FileFormat(c.data[14]>>2) & 0b11 } // TmpWriteProtected indicates temporary protection over the entire card content from being overwritten or erased. func (c *CSD) TmpWriteProtected() bool { return c.data[14]&(1<<4) != 0 } // PermWriteProtected indicates permanent protecttion of entire card content against overwriting or erasing (write+erase permanently disabled). func (c *CSD) PermWriteProtected() bool { return c.data[14]&(1<<5) != 0 } // IsCopy whether contents are original or have been copied. func (c *CSD) IsCopy() bool { return c.data[14]&(1<<6) != 0 } // FileFormatGroup returns the file format group bit, which selects between // the two [FileFormat] tables. Interpret together with [CSD.FileFormat]. func (c *CSD) FileFormatGroup() bool { return c.data[14]&(1<<7) != 0 } // DeviceCapacity returns the total device capacity in bytes, dispatching on // the CSD version. Returns 0 for unknown CSD versions. func (c *CSD) DeviceCapacity() (size int64) { switch c.csdStructure() { case 0: v1 := c.MustV1() size = int64(v1.DeviceCapacity()) case 1: v2 := c.MustV2() size = v2.DeviceCapacity() } return size } // NumberOfBlocks returns amount of readable blocks in the device given by Capacity/ReadBlockLength. func (c *CSD) NumberOfBlocks() (numBlocks int64) { rblocks := c.ReadBlockLen() if rblocks == 0 { return 0 } return c.DeviceCapacity() / int64(rblocks) } // After byte 5 CSDv1 and CSDv2 differ in structure at some fields. // DeviceCapacity returns the device capacity in bytes: // (C_SIZE+1) * 512KiB, as per section 5.3.3 of the SD Simplified Specification. func (c *CSDv2) DeviceCapacity() int64 { csize := c.csize() return (int64(csize) + 1) * (512 * 1024) } // csize returns the 22-bit C_SIZE field (CSD bits 69:48). func (c *CSDv2) csize() uint32 { return uint32(c.data[7]&0x3F)<<16 | uint32(c.data[8])<<8 | uint32(c.data[9]) } // DeviceCapacity returns the total memory capacity of the SDCard in bytes. Max is 2GB for V1. func (c *CSDv1) DeviceCapacity() uint32 { mult := c.mult() csize := c.csize() blklen := c.ReadBlockLen() blockNR := uint32(csize+1) * uint32(mult) return blockNR * uint32(blklen) } func (c *CSDv1) csize() uint16 { // Jesus, why did SD make this so complicated? return uint16(c.data[8]>>6) | uint16(c.data[7])<<2 | uint16(c.data[6]&0b11)<<10 } // mult is a factor for computing total device size with csize and csizemult. func (c *CSDv1) mult() uint16 { return 1 << (2 + c.csizemult()) } func (c *CSDv1) csizemult() uint8 { return (c.data[9]&0b11)<<1 | (c.data[10] >> 7) } // VddReadCurrent indicates min and max values for read power supply currents. // - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA // - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA func (c *CSDv1) VddReadCurrent() (min, max uint8) { return (c.data[8] >> 3) & 0b111, c.data[8] & 0b111 } // VddWriteCurrent indicates min and max values for write power supply currents. // - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA // - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA func (c *CSDv1) VddWriteCurrent() (min, max uint8) { return c.data[9] >> 5, (c.data[9] >> 3) & 0b111 } // String returns a human-readable multi-line summary of the CSD fields. func (c *CSD) String() string { version := c.csdStructure() + 1 if version > 2 { return "" } const delim = '\n' buf := make([]byte, 0, 64) buf = c.appendf(buf, delim) return string(buf) } func (c *CSDv1) String() string { return c.CSD.String() } func (c *CSDv2) String() string { return c.CSD.String() } func (c *CSD) appendf(b []byte, delim byte) []byte { b = appendnum(b, "Version", uint64(c.Version()), delim) b = appendnum(b, "Capacity(bytes)", uint64(c.DeviceCapacity()), delim) b = appendnum(b, "TimeAccess_ns", uint64(c.TAAC().AccessTime()), delim) b = appendnum(b, "NSAC", uint64(c.NSAC()), delim) b = appendnum(b, "Tx_kb/s", uint64(c.TransferSpeed().RateKilobits()), delim) b = appendnum(b, "CCC", uint64(c.CommandClasses()), delim) b = appendnum(b, "ReadBlockLen", uint64(c.ReadBlockLen()), delim) b = appendbit(b, "ReadBlockPartial", c.AllowsReadBlockPartial(), delim) b = appendbit(b, "AllowWriteBlockMisalignment", c.AllowsWriteBlockMisalignment(), delim) b = appendbit(b, "AllowReadBlockMisalignment", c.AllowsReadBlockMisalignment(), delim) b = appendbit(b, "ImplementsDSR", c.ImplementsDSR(), delim) b = appendnum(b, "WProtectNumSectors", uint64(c.WriteProtectGroupSizeInSectors()), delim) b = appendnum(b, "WriteBlockLen", uint64(c.WriteBlockLen()), delim) b = appendbit(b, "WGrpEnable", c.WriteGroupEnabled(), delim) b = appendbit(b, "WPartialAllow", c.AllowsWritePartial(), delim) b = append(b, "FileFmt:"...) b = append(b, c.FileFormat().String()...) b = append(b, delim) b = appendbit(b, "TmpWriteProtect", c.TmpWriteProtected(), delim) b = appendbit(b, "PermWriteProtect", c.PermWriteProtected(), delim) b = appendbit(b, "IsCopy", c.IsCopy(), delim) b = appendbit(b, "FileFormatGrp", c.FileFormatGroup(), delim) return b } func appendnum(b []byte, label string, n uint64, delim byte) []byte { b = append(b, label...) b = append(b, ':') b = strconv.AppendUint(b, n, 10) b = append(b, delim) return b } func appendbit(b []byte, label string, n bool, delim byte) []byte { b = append(b, label...) b = append(b, ':') b = append(b, '0'+b2u8(n)) b = append(b, delim) return b } func upToNull(buf []byte) []byte { nullIdx := bytes.IndexByte(buf, 0) if nullIdx < 0 { return buf } return buf[:nullIdx] } type ( command byte appcommand byte ) // SD commands and application commands. const ( cmdGoIdleState command = 0 cmdSendOpCnd command = 1 cmdAllSendCID command = 2 cmdSendRelativeAddr command = 3 cmdSetDSR command = 4 cmdSwitchFunc command = 6 cmdSelectDeselectCard command = 7 cmdSendIfCond command = 8 cmdSendCSD command = 9 cmdSendCID command = 10 cmdStopTransmission command = 12 cmdSendStatus command = 13 cmdGoInactiveState command = 15 cmdSetBlocklen command = 16 cmdReadSingleBlock command = 17 cmdReadMultipleBlock command = 18 cmdWriteBlock command = 24 cmdWriteMultipleBlock command = 25 cmdProgramCSD command = 27 cmdSetWriteProt command = 28 cmdClrWriteProt command = 29 cmdSendWriteProt command = 30 cmdEraseWrBlkStartAddr command = 32 cmdEraseWrBlkEndAddr command = 33 cmdErase command = 38 cmdLockUnlock command = 42 cmdAppCmd command = 55 cmdGenCmd command = 56 cmdReadOCR command = 58 cmdCRCOnOff command = 59 acmdSET_BUS_WIDTH appcommand = 6 acmdSD_STATUS appcommand = 13 acmdSEND_NUM_WR_BLOCKS appcommand = 22 acmdSET_WR_BLK_ERASE_COUNT appcommand = 23 acmdSD_APP_OP_COND appcommand = 41 acmdSET_CLR_CARD_DETECT appcommand = 42 acmdSEND_SCR appcommand = 51 acmdSECURE_READ_MULTI_BLOCK appcommand = 18 acmdSECURE_WRITE_MULTI_BLOCK appcommand = 25 acmdSECURE_WRITE_MKB appcommand = 26 acmdSECURE_ERASE appcommand = 38 acmdGET_MKB appcommand = 43 acmdGET_MID appcommand = 44 acmdSET_CER_RN1 appcommand = 45 acmdSET_CER_RN2 appcommand = 46 acmdSET_CER_RES2 appcommand = 47 acmdSET_CER_RES1 appcommand = 48 acmdCHANGE_SECURE_AREA appcommand = 49 ) // CSD field types. type ( // TransferSpeed is the TRAN_SPEED CSD field: the maximum data transfer // rate encoded as a rate unit (lower 3 bits) and time value multiplier. TransferSpeed uint8 // TAAC is the data read access time CSD field, encoded as a time unit // (lower 3 bits) and time value multiplier. TAAC uint8 // FileFormat is the format of the data stored on the card. See the // FileFmt* constants for possible values. FileFormat uint8 // CommandClasses is the CCC CSD field, a bitfield where bit position i // set means command class i is supported. CommandClasses uint16 // NSAC is the data read access time 2 CSD field, given in units of // 100 clock cycles. NSAC uint8 ) const ( FileFmtPartition FileFormat = iota // Hard disk like file system with partition table. FileFmtDOSFAT // DOS FAT (floppy like) FileFmtUFF // Universal File Format FileFmtUnknown ) // String returns a human-readable name for the file format. func (ff FileFormat) String() (s string) { switch ff { case FileFmtPartition: s = "partition" case FileFmtDOSFAT: s = "DOS/FAT" case FileFmtUFF: s = "UFF" case FileFmtUnknown: s = "unknown" default: s = "" } return s } var log10table = [...]int64{ 1, 10, 100, 1000, 10000, 100000, 1000000, } // RateKilobits returns the transfer rate in kilobits per second. func (t TransferSpeed) RateKilobits() int64 { return 100 * log10table[t&0b111] } // AccessTime returns the asynchronous part of the data access time. func (t TAAC) AccessTime() (d time.Duration) { return time.Duration(log10table[t&0b111]) * time.Nanosecond } func b2u8(b bool) uint8 { if b { return 1 } return 0 } // CRC16 computes the CRC16 checksum for a given payload using the CRC-16-CCITT polynomial. func CRC16(buf []byte) (crc uint16) { const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1 for _, b := range buf { crc ^= (uint16(b) << 8) // Shift byte into MSB of crc for i := 0; i < 8; i++ { // Process each bit if crc&0x8000 != 0 { crc = (crc << 1) ^ poly } else { crc <<= 1 } } } return crc } // CRC7 computes the CRC7 checksum for a given payload using the polynomial x^7 + x^3 + 1. func CRC7(data []byte) (crc uint8) { return crc7noshift(data) >> 1 } func crc7noshift(data []byte) (crc uint8) { for _, b := range data { crc = crc7_table[crc^b] } return crc } var crc7_table = [256]byte{ 0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee, 0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc, 0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a, 0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8, 0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26, 0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14, 0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42, 0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70, 0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c, 0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e, 0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08, 0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a, 0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4, 0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96, 0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0, 0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2, }