diff --git a/examples/sd/main.go b/examples/sd/main.go new file mode 100644 index 000000000..706f50c06 --- /dev/null +++ b/examples/sd/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/sd" +) + +const ( + SPI_RX_PIN = machine.GP16 + SPI_TX_PIN = machine.GP19 + SPI_SCK_PIN = machine.GP18 + SPI_CS_PIN = machine.GP15 +) + +var ( + spibus = machine.SPI0 + spicfg = machine.SPIConfig{ + Frequency: 250000, + Mode: 0, + SCK: SPI_SCK_PIN, + SDO: SPI_TX_PIN, + SDI: SPI_RX_PIN, + } +) + +func main() { + time.Sleep(time.Second) + SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + err := spibus.Configure(spicfg) + if err != nil { + panic(err.Error()) + } + sdcard := sd.NewSPICard(spibus, SPI_CS_PIN.Set) + println("start init") + err = sdcard.Init() + if err != nil { + panic("sd card init:" + err.Error()) + } + // After initialization it's safe to increase SPI clock speed. + csd := sdcard.CSD() + kbps := csd.TransferSpeed().RateKilobits() + spicfg.Frequency = uint32(kbps * 1000) + err = spibus.Configure(spicfg) + + cid := sdcard.CID() + fmt.Printf("name=%s\ncsd=\n%s\n", cid.ProductName(), csd.String()) + + bd, err := sd.NewBlockDevice(sdcard, csd.ReadBlockLen(), csd.NumberOfBlocks()) + if err != nil { + panic("block device creation:" + err.Error()) + } + var mc MemChecker + + ok, badBlkIdx, err := mc.MemCheck(bd, 2, 100) + if err != nil { + panic("memcheck:" + err.Error()) + } + if !ok { + println("bad block", badBlkIdx) + } else { + println("memcheck ok") + } +} + +type MemChecker struct { + rdBuf []byte + storeBuf []byte + wrBuf []byte +} + +func (mc *MemChecker) MemCheck(bd *sd.BlockDevice, blockIdx, numBlocks int64) (memOK bool, badBlockIdx int64, err error) { + size := bd.BlockSize() * numBlocks + if len(mc.rdBuf) < int(size) { + mc.rdBuf = make([]byte, size) + mc.wrBuf = make([]byte, size) + mc.storeBuf = make([]byte, size) + for i := range mc.wrBuf { + mc.wrBuf[i] = byte(i) + } + } + // Start by storing the original block contents. + _, err = bd.ReadAt(mc.storeBuf, blockIdx) + if err != nil { + return false, blockIdx, err + } + + // Write the test pattern. + _, err = bd.WriteAt(mc.wrBuf, blockIdx) + if err != nil { + return false, blockIdx, err + } + // Read back the test pattern. + _, err = bd.ReadAt(mc.rdBuf, blockIdx) + if err != nil { + return false, blockIdx, err + } + for j := 0; j < len(mc.rdBuf); j++ { + // Compare the read back data with the test pattern. + if mc.rdBuf[j] != mc.wrBuf[j] { + badBlock := blockIdx + int64(j)/bd.BlockSize() + return false, badBlock, nil + } + mc.rdBuf[j] = 0 + } + // Leave the card in it's previous state. + _, err = bd.WriteAt(mc.storeBuf, blockIdx) + return true, -1, nil +} diff --git a/sd/README.md b/sd/README.md new file mode 100644 index 000000000..54c71b2ae --- /dev/null +++ b/sd/README.md @@ -0,0 +1,11 @@ +## `sd` package + +File map: +* `blockdevice.go`: Contains logic for creating an `io.WriterAt` and `io.ReaderAt` with the `sd.BlockDevice` concrete type + from the `sd.Card` interface which is intrinsically a blocked reader and writer. + +* `spicard.go`: Contains the `sd.SpiCard` driver for controlling an SD card over SPI using the most commonly available circuit boards. + +* `responses.go`: Contains a currently unused SD response implementations as per the latest specification. + +* `definitions.go`: Contains SD Card specification definitions such as the CSD and CID types as well as encoding/decoding logic, as well as CRC logic. diff --git a/sd/blockdevice.go b/sd/blockdevice.go new file mode 100644 index 000000000..f3cabac07 --- /dev/null +++ b/sd/blockdevice.go @@ -0,0 +1,218 @@ +package sd + +import ( + "errors" + "io" + "math/bits" +) + +var ( + errNegativeOffset = errors.New("sd: negative offset") +) + +// Compile time guarantee of interface implementation. +var _ Card = (*SPICard)(nil) +var _ io.ReaderAt = (*BlockDevice)(nil) +var _ io.WriterAt = (*BlockDevice)(nil) + +type Card interface { + // WriteBlocks writes the given data to the card, starting at the given block index. + // The data must be a multiple of the block size. + WriteBlocks(data []byte, startBlockIdx int64) (int, error) + // ReadBlocks reads the given number of blocks from the card, starting at the given block index. + // The dst buffer must be a multiple of the block size. + ReadBlocks(dst []byte, startBlockIdx int64) (int, error) + // EraseBlocks erases blocks starting at startBlockIdx to startBlockIdx+numBlocks. + EraseBlocks(startBlock, numBlocks int64) error +} + +// NewBlockDevice creates a new BlockDevice from a Card. +func NewBlockDevice(card Card, blockSize int, numBlocks int64) (*BlockDevice, error) { + if card == nil || blockSize <= 0 || numBlocks <= 0 { + return nil, errors.New("invalid argument(s)") + } + blk, err := makeBlockIndexer(blockSize) + if err != nil { + return nil, err + } + bd := &BlockDevice{ + card: card, + blockbuf: make([]byte, blockSize), + blk: blk, + numblocks: int64(numBlocks), + } + return bd, nil +} + +// BlockDevice implements tinyfs.BlockDevice interface for an [sd.Card] type. +type BlockDevice struct { + card Card + blockbuf []byte + blk blkIdxer + numblocks int64 +} + +// ReadAt implements [io.ReadAt] interface for an SD card. +func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if off < 0 { + return 0, errNegativeOffset + } + + blockIdx := bd.blk.idx(off) + blockOff := bd.blk.off(off) + if blockOff != 0 { + // Non-aligned first block case. + if _, err = bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + n += copy(p, bd.blockbuf[blockOff:]) + p = p[n:] + blockIdx++ + } + + fullBlocksToRead := bd.blk.idx(int64(len(p))) + if fullBlocksToRead > 0 { + // 1 or more full blocks case. + endOffset := fullBlocksToRead * bd.blk.size() + ngot, err := bd.card.ReadBlocks(p[:endOffset], blockIdx) + if err != nil { + return n + ngot, err + } + p = p[endOffset:] + n += ngot + blockIdx += fullBlocksToRead + } + + if len(p) > 0 { + // Non-aligned last block case. + if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + n += copy(p, bd.blockbuf) + } + return n, nil +} + +// WriteAt implements [io.WriterAt] interface for an SD card. +func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if off < 0 { + return 0, errNegativeOffset + } + + blockIdx := bd.blk.idx(off) + blockOff := bd.blk.off(off) + if blockOff != 0 { + // Non-aligned first block case. + if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + nexpect := copy(bd.blockbuf[blockOff:], p) + ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx) + if err != nil { + return n, err + } else if ngot != len(bd.blockbuf) { + return n, io.ErrShortWrite + } + n += nexpect + p = p[nexpect:] + blockIdx++ + } + + fullBlocksToWrite := bd.blk.idx(int64(len(p))) + if fullBlocksToWrite > 0 { + // 1 or more full blocks case. + endOffset := fullBlocksToWrite * bd.blk.size() + ngot, err := bd.card.WriteBlocks(p[:endOffset], blockIdx) + n += ngot + if err != nil { + return n, err + } else if ngot != int(endOffset) { + return n, io.ErrShortWrite + } + p = p[ngot:] + blockIdx += fullBlocksToWrite + } + + if len(p) > 0 { + // Non-aligned last block case. + if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + copy(bd.blockbuf, p) + ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx) + if err != nil { + return n, err + } else if ngot != len(bd.blockbuf) { + return n, io.ErrShortWrite + } + n += len(p) + } + return n, nil +} + +// Size returns the number of bytes in this block device. +func (bd *BlockDevice) Size() int64 { + return bd.BlockSize() * bd.numblocks +} + +// BlockSize returns the size of a block in bytes. +func (bd *BlockDevice) BlockSize() int64 { + return bd.blk.size() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (bd *BlockDevice) EraseBlocks(startEraseBlockIdx, len int64) error { + return bd.card.EraseBlocks(startEraseBlockIdx, len) +} + +// blkIdxer is a helper for calculating block indices and offsets. +type blkIdxer struct { + blockshift int64 + blockmask int64 +} + +func makeBlockIndexer(blockSize int) (blkIdxer, error) { + if blockSize <= 0 { + return blkIdxer{}, errNoblocks + } + tz := bits.TrailingZeros(uint(blockSize)) + if blockSize>>tz != 1 { + return blkIdxer{}, errors.New("blockSize must be a power of 2") + } + blk := blkIdxer{ + blockshift: int64(tz), + blockmask: (1 << tz) - 1, + } + return blk, nil +} + +// size returns the size of a block in bytes. +func (blk *blkIdxer) size() int64 { + return 1 << blk.blockshift +} + +// off gets the offset of the byte at byteIdx from the start of its block. +// +//go:inline +func (blk *blkIdxer) off(byteIdx int64) int64 { + return blk._moduloBlockSize(byteIdx) +} + +// idx gets the block index that contains the byte at byteIdx. +// +//go:inline +func (blk *blkIdxer) idx(byteIdx int64) int64 { + return blk._divideBlockSize(byteIdx) +} + +// modulo and divide are defined in terms of bit operations for speed since +// blockSize is a power of 2. + +//go:inline +func (blk *blkIdxer) _moduloBlockSize(n int64) int64 { return n & blk.blockmask } + +//go:inline +func (blk *blkIdxer) _divideBlockSize(n int64) int64 { return n >> blk.blockshift } diff --git a/sd/card_test.go b/sd/card_test.go new file mode 100644 index 000000000..2192c65e1 --- /dev/null +++ b/sd/card_test.go @@ -0,0 +1,95 @@ +package sd + +import ( + "encoding/hex" + "testing" +) + +func TestCRC16(t *testing.T) { + tests := []struct { + block string + wantcrc uint16 + }{ + { + block: "fa33c08ed0bc007c8bf45007501ffbfcbf0006b90001f2a5ea1d060000bebe07b304803c80740e803c00751c83c610fecb75efcd188b148b4c028bee83c610fecb741a803c0074f4be8b06ac3c00740b56bb0700b40ecd105eebf0ebfebf0500bb007cb8010257cd135f730c33c0cd134f75edbea306ebd3bec206bffe7d813d55aa75c78bf5ea007c0000496e76616c696420706172746974696f6e207461626c65004572726f72206c6f6164696e67206f7065726174696e672073797374656d004d697373696e67206f7065726174696e672073797374656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000094c6dffd0000000401040cfec2ff000800000000f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055aa", + wantcrc: 0x52ce, + }, + } + + for _, tt := range tests { + b, err := hex.DecodeString(tt.block) + if err != nil { + t.Fatal(err) + } + gotcrc := CRC16(b) + if gotcrc != tt.wantcrc { + t.Errorf("calculateCRC(%s) = %#x, want %#x", tt.block, gotcrc, tt.wantcrc) + } + } +} + +func TestCRC7(t *testing.T) { + const cmdSendMask = 0x40 + tests := []struct { + data []byte + wantCRC uint8 + }{ + { // See CRC7 Examples from section 4.5 of the SD Card Physical Layer Simplified Specification. + data: []byte{cmdSendMask, 4: 0}, // CMD0, arg=0 + wantCRC: 0b1001010, + }, + { + data: []byte{cmdSendMask | 17, 4: 0}, // CMD17, arg=0 + wantCRC: 0b0101010, + }, + { + data: []byte{17, 3: 0b1001, 4: 0}, // Response of CMD17 + wantCRC: 0b0110011, + }, + { // CSD for a 8GB card. + data: []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0}, + wantCRC: 0b1110010, + }, + } + + for _, tt := range tests { + gotcrc := CRC7(tt.data[:]) + if gotcrc != tt.wantCRC { + t.Errorf("got crc=%#b, want=%#b for %#b", gotcrc, tt.wantCRC, tt.data) + } + } + + cmdTests := []struct { + cmd command + arg uint32 + wantCRC uint8 + }{ + { + cmd: cmdGoIdleState, + arg: 0, + wantCRC: 0x95, + }, + { + cmd: cmdSendIfCond, + arg: 0x1AA, + wantCRC: 0x87, + }, + } + var dst [6]byte + for _, test := range cmdTests { + putCmd(dst[:], test.cmd, test.arg) + gotcrc := dst[5] + if gotcrc != test.wantCRC { + t.Errorf("got crc=%#x, want=%#x", gotcrc, test.wantCRC) + } + } +} + +func putCmd(dst []byte, cmd command, arg uint32) { + dst[0] = byte(cmd) | (1 << 6) + dst[1] = byte(arg >> 24) + dst[2] = byte(arg >> 16) + dst[3] = byte(arg >> 8) + dst[4] = byte(arg) + dst[5] = crc7noshift(dst[:5]) | 1 // Stop bit added. +} diff --git a/sd/definitions.go b/sd/definitions.go new file mode 100644 index 000000000..f58d3ed0c --- /dev/null +++ b/sd/definitions.go @@ -0,0 +1,546 @@ +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 + +type CardKind uint8 + +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 +) + +type CID struct { + data [16]byte +} + +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]) +} + +// The product name is a string, 5-character ASCII string. +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 +} + +// The Serial Number is 32 bits of binary number. +func (c *CID) ProductSerialNumber() uint32 { + return binary.BigEndian.Uint32(c.data[9:13]) +} + +// ManufacturingDate returns the manufacturing date of the card. +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} +} + +func (c CSD) MustV2() CSDv2 { + if c.csdStructure() != 1 { + panic("CSD is not version 2.0") + } + return CSDv2{CSD: c} +} + +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. +// This is a bitfield, each bit position indicates whether the +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() } +func (c *CSD) ReadBlockLenShift() uint8 { return c.data[5] & 0x0F } + +// AllowsReadBlockPartial should always return true. Indicates that +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 } + +// EraseSectorSizeInBlocks represents how much memory is erased in an erase +// command in multiple of block size. +func (c *CSDv1) EraseSectorSizeInBytes() int64 { + blklen := c.WriteBlockLen() + numblocks := c.SectorSize() + return int64(numblocks) * blklen +} + +// SectorSize varies in meaning depending on the version. +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 } + +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 } + +func (c *CSD) FileFormatGroup() bool { return c.data[14]&(1<<7) != 0 } + +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. +func (c *CSDv2) DeviceCapacity() int64 { + csize := c.csize() + return int64(csize) * 512_000 +} + +func (c *CSDv2) csize() uint32 { + return uint32(c.data[7]>>2)<<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 +} + +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 enum types. +type ( + TransferSpeed uint8 + TAAC uint8 + FileFormat uint8 + CommandClasses uint16 + NSAC uint8 +) + +const ( + FileFmtPartition FileFormat = iota // Hard disk like file system with partition table. + FileFmtDOSFAT // DOS FAT (floppy like) + FileFmtUFF // Universal File Format + FileFmtUnknown +) + +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, +} + +// RateMegabits returns the transfer rate in kilobits per second. +func (t TransferSpeed) RateKilobits() int64 { + return 100 * log10table[t&0b111] +} + +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, +} diff --git a/sd/responses.go b/sd/responses.go new file mode 100644 index 000000000..90f74e919 --- /dev/null +++ b/sd/responses.go @@ -0,0 +1,319 @@ +package sd + +import ( + "encoding/binary" + "strconv" +) + +const ( + _CMD_TIMEOUT = 100 + + _R1_IDLE_STATE = 1 << 0 + _R1_ERASE_RESET = 1 << 1 + _R1_ILLEGAL_COMMAND = 1 << 2 + _R1_COM_CRC_ERROR = 1 << 3 + _R1_ERASE_SEQUENCE_ERROR = 1 << 4 + _R1_ADDRESS_ERROR = 1 << 5 + _R1_PARAMETER_ERROR = 1 << 6 + + _DATA_RES_MASK = 0x1F + _DATA_RES_ACCEPTED = 0x05 +) + +type response1 uint8 + +func (r response1) IsIdle() bool { return r&_R1_IDLE_STATE != 0 } +func (r response1) IllegalCmdError() bool { return r&_R1_ILLEGAL_COMMAND != 0 } +func (r response1) CRCError() bool { return r&_R1_COM_CRC_ERROR != 0 } +func (r response1) EraseReset() bool { return r&_R1_ERASE_RESET != 0 } +func (r response1) EraseSeqError() bool { return r&_R1_ERASE_SEQUENCE_ERROR != 0 } +func (r response1) AddressError() bool { return r&_R1_ADDRESS_ERROR != 0 } +func (r response1) ParamError() bool { return r&_R1_PARAMETER_ERROR != 0 } + +type response1Err struct { + context string + status response1 +} + +func (e response1Err) Error() string { + return e.status.Response() + if e.context != "" { + return "sd:" + e.context + " " + strconv.Itoa(int(e.status)) + } + return "sd:status " + strconv.Itoa(int(e.status)) +} + +func (e response1) Response() string { + b := make([]byte, 0, 8) + return string(e.appendf(b)) +} + +func (r response1) appendf(b []byte) []byte { + b = append(b, '[') + if r.IsIdle() { + b = append(b, "idle,"...) + } + if r.EraseReset() { + b = append(b, "erase-rst,"...) + } + if r.EraseSeqError() { + b = append(b, "erase-seq,"...) + } + if r.CRCError() { + b = append(b, "crc-err,"...) + } + if r.AddressError() { + b = append(b, "addr-err,"...) + } + if r.ParamError() { + b = append(b, "param-err,"...) + } + if r.IllegalCmdError() { + b = append(b, "illegal-cmd,"...) + } + if len(b) > 1 { + b = b[:len(b)-1] + } + b = append(b, ']') + return b +} + +func makeResponseError(status response1) error { + return response1Err{ + status: status, + } +} + +// Commands used to help generate this file: +// - stringer -type=state -trimprefix=state -output=state_string.go +// - stringer -type=status -trimprefix=status -output=status_string.go + +// Tokens that are sent by card during polling. +// https://github.com/arduino-libraries/SD/blob/master/src/utility/SdInfo.h +const ( + tokSTART_BLOCK = 0xfe + tokSTOP_TRAN = 0xfd + tokWRITE_MULT = 0xfc +) + +type state uint8 + +const ( + stateIdle state = iota + stateReady + stateIdent + stateStby + stateTran + stateData + stateRcv + statePrg + stateDis +) + +// status represents the Card Status Register (R1), as per section 4.10.1. +type status uint32 + +func (s status) state() state { + return state(s >> 9 & 0xf) +} + +// First status bits. +const ( + statusRsvd0 status = iota + statusRsvd1 + statusRsvd2 + statusAuthSeqError + statusRsvdSDIO + statusAppCmd + statusFXEvent + statusRsvd7 + statusReadyForData +) + +// Upper bound status bits. +const ( + statusEraseReset status = iota + 13 + statusECCDisabled + statusWPEraseSkip + statusCSDOverwrite + _ + _ + statusGenericError + statusControllerError // internal card controller error + statusECCFailed + statusIllegalCommand + statusComCRCError // CRC check of previous command failed + statusLockUnlockFailed + statusCardIsLocked // Signals that the card is locked by the host. + statusWPViolation // Write protected violation + statusEraseParamError // invalid write block selection for erase + statusEraseSeqError // error in erase sequence + statusBlockLenError // tx block length not allowed + statusAddrError // misaligned address + statusAddrOutOfRange // address out of range +) + +// r1 is the normal response to a command. +type r1 struct { + data [48 / 8]byte // 48 bits of response. +} + +func (r *r1) RawCopy() [6]byte { return r.data } +func (r *r1) startbit() bool { + return r.data[0]&(1<<7) != 0 +} +func (r *r1) txbit() bool { + return r.data[0]&(1<<6) != 0 +} +func (r *r1) cmdidx() uint8 { + return r.data[0] & 0b11_1111 +} +func (r *r1) cardstatus() status { + return status(binary.BigEndian.Uint32(r.data[1:5])) +} +func (r *r1) CRC7() uint8 { return r.data[5] >> 1 } +func (r *r1) endbit() bool { return r.data[5]&1 != 0 } + +func (r *r1) IsValid() bool { + return r.endbit() && CRC7(r.data[:5]) == r.CRC7() +} + +type r6 struct { + data [48 / 8]byte +} + +func (r *r6) RawCopy() [6]byte { return r.data } +func (r *r6) startbit() bool { + return r.data[0]&(1<<7) != 0 +} +func (r *r6) txbit() bool { + return r.data[0]&(1<<6) != 0 +} +func (r *r6) cmdidx() uint8 { + return r.data[0] & 0b11_1111 +} +func (r *r6) rca() uint16 { + return binary.BigEndian.Uint16(r.data[1:3]) +} +func (r *r6) CardStatus() status { + moveBit := func(b status, from, to uint) status { + return (b & (1 << from)) >> from << to + } + // See 4.9.5 R6 (Published RCA response) of the SD Simplified Specification. + s := status(binary.BigEndian.Uint16(r.data[1:5])) + s = moveBit(s, 13, 19) + s = moveBit(s, 14, 22) + s = moveBit(s, 15, 23) + return s +} +func (r *r6) CRC7() uint8 { return r.data[5] >> 1 } +func (r *r6) endbit() bool { return r.data[5]&1 != 0 } + +func (r *r6) IsValid() bool { + return r.endbit() && CRC7(r.data[:5]) == r.CRC7() +} + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[statusRsvd0-0] + _ = x[statusRsvd1-1] + _ = x[statusRsvd2-2] + _ = x[statusAuthSeqError-3] + _ = x[statusRsvdSDIO-4] + _ = x[statusAppCmd-5] + _ = x[statusFXEvent-6] + _ = x[statusRsvd7-7] + _ = x[statusReadyForData-8] + _ = x[statusEraseReset-13] + _ = x[statusECCDisabled-14] + _ = x[statusWPEraseSkip-15] + _ = x[statusCSDOverwrite-16] + _ = x[statusGenericError-19] + _ = x[statusControllerError-20] + _ = x[statusECCFailed-21] + _ = x[statusIllegalCommand-22] + _ = x[statusComCRCError-23] + _ = x[statusLockUnlockFailed-24] + _ = x[statusCardIsLocked-25] + _ = x[statusWPViolation-26] + _ = x[statusEraseParamError-27] + _ = x[statusEraseSeqError-28] + _ = x[statusBlockLenError-29] + _ = x[statusAddrError-30] + _ = x[statusAddrOutOfRange-31] +} + +const ( + _status_name_0 = "Rsvd0Rsvd1Rsvd2AuthSeqErrorRsvdSDIOAppCmdFXEventRsvd7ReadyForData" + _status_name_1 = "EraseResetECCDisabledWPEraseSkipCSDOverwrite" + _status_name_2 = "GenericErrorControllerErrorECCFailedIllegalCommandComCRCErrorLockUnlockFailedCardIsLockedWPViolationEraseParamErrorEraseSeqErrorBlockLenErrorAddrErrorAddrOutOfRange" +) + +var ( + _status_index_0 = [...]uint8{0, 5, 10, 15, 27, 35, 41, 48, 53, 65} + _status_index_1 = [...]uint8{0, 10, 21, 32, 44} + _status_index_2 = [...]uint8{0, 12, 27, 36, 50, 61, 77, 89, 100, 115, 128, 141, 150, 164} +) + +func (i status) string() string { + switch { + case i <= 8: + return _status_name_0[_status_index_0[i]:_status_index_0[i+1]] + case 13 <= i && i <= 16: + i -= 13 + return _status_name_1[_status_index_1[i]:_status_index_1[i+1]] + case 19 <= i && i <= 31: + i -= 19 + return _status_name_2[_status_index_2[i]:_status_index_2[i+1]] + default: + return "" + } +} + +func (s status) String() string { + return string(s.appendf(nil, ',')) +} + +func (s status) appendf(b []byte, delim byte) []byte { + b = append(b, s.state().String()...) + b = append(b, '[') + if s == 0 { + return append(b, ']') + } + for bit := 0; bit < 32; bit++ { + if s&(1<= state(len(_state_index)-1) { + return "" + } + return _state_name[_state_index[i]:_state_index[i+1]] +} diff --git a/sd/spicard.go b/sd/spicard.go new file mode 100644 index 000000000..89813572a --- /dev/null +++ b/sd/spicard.go @@ -0,0 +1,545 @@ +package sd + +import ( + "encoding/binary" + "errors" + "io" + "math" + "time" + + "tinygo.org/x/drivers" +) + +// See rustref.go for the new implementation. + +var ( + errBadCSDCID = errors.New("sd:bad CSD/CID in CRC or always1") + errNoSDCard = errors.New("sd:no card") + errCardNotSupported = errors.New("sd:card not supported") + errWaitStartBlock = errors.New("sd:did not find start block token") + errNeedBlockLenMultiple = errors.New("sd:need blocksize multiple for I/O") + errWrite = errors.New("sd:write") + errWriteTimeout = errors.New("sd:write timeout") + errReadTimeout = errors.New("sd:read timeout") + errBusyTimeout = errors.New("sd:busy card timeout") + errOOB = errors.New("sd:oob block access") + errNoblocks = errors.New("sd:no readable blocks") +) + +type digitalPinout = func(b bool) + +type SPICard struct { + bus drivers.SPI + cs digitalPinout + + timers [2]timer + timeout time.Duration + wait time.Duration + // Card Identification Register. + cid CID + // Card Specific Register. + csd CSD + bufcmd [6]byte + kind CardKind + // block indexing helper based on block size. + blk blkIdxer + lastCRC uint16 +} + +func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { + const defaultTimeout = 300 * time.Millisecond + s := &SPICard{ + bus: spi, + cs: cs, + } + s.setTimeout(defaultTimeout) + return s +} + +// setTimeout sets the timeout for all operations and the wait time between each yield during busy spins. +func (c *SPICard) setTimeout(timeout time.Duration) { + if timeout <= 0 { + panic("timeout must be positive") + } + c.timeout = timeout + c.wait = timeout / 512 +} + +// LastReadCRC returns the CRC for the last ReadBlock operation. +func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC } + +// Init initializes the SD card. This routine should be performed with a SPI clock +// speed of around 100..400kHz. One may increase the clock speed after initialization. +func (d *SPICard) Init() error { + return d.initRs() +} + +func (d *SPICard) NumberOfBlocks() int64 { + return d.csd.NumberOfBlocks() +} + +// CID returns a copy of the Card Identification Register value last read. +func (d *SPICard) CID() CID { return d.cid } + +// CSD returns a copy of the Card Specific Data Register value last read. +func (d *SPICard) CSD() CSD { return d.csd } + +func (d *SPICard) yield() { time.Sleep(d.wait) } + +type timer struct { + deadline time.Time +} + +func (t *timer) setTimeout(timeout time.Duration) *timer { + t.deadline = time.Now().Add(timeout) + return t +} + +func (t timer) expired() bool { + return time.Since(t.deadline) >= 0 +} + +// Reference for this implementation: +// https://github.com/embassy-rs/embedded-sdmmc-rs/blob/master/src/sdmmc.rs + +// Not used currently. We'd want to switch over to one way of doing things, Rust way. +func (d *SPICard) initRs() error { + // Supply minimum of 74 clock cycles with CS high. + d.csEnable(true) + for i := 0; i < 10; i++ { + d.send(0xff) + } + d.csEnable(false) + for i := 0; i < 512; i++ { + d.receive() + } + d.csEnable(true) + defer d.csEnable(false) + // Enter SPI mode + const maxRetries = 32 + retries := maxRetries + tm := d.timers[0].setTimeout(2 * time.Second) + for retries > 0 { + stat, err := d.card_command(cmdGoIdleState, 0) // CMD0. + if err != nil { + if isTimeout(err) { + retries-- + continue // Try again! + } + return err + } + if stat == _R1_IDLE_STATE { + break + } else if tm.expired() { + retries = 0 + break + } + retries-- + } + if retries <= 0 { + return errNoSDCard + } + const enableCRC = true + if enableCRC { + stat, err := d.card_command(cmdCRCOnOff, 1) // CMD59. + if err != nil { + return err + } else if stat != _R1_IDLE_STATE { + return errors.New("sd:cant enable CRC") + } + } + + tm.setTimeout(time.Second) + for { + stat, err := d.card_command(cmdSendIfCond, 0x1AA) // CMD8. + if err != nil { + return err + } else if stat == (_R1_ILLEGAL_COMMAND | _R1_IDLE_STATE) { + d.kind = TypeSD1 + break + } + d.receive() + d.receive() + d.receive() + status, err := d.receive() + if err != nil { + return err + } + if status == 0xaa { + d.kind = TypeSD2 + break + } + d.yield() + } + + var arg uint32 + if d.kind != TypeSD1 { + arg = 0x4000_0000 + } + tm.setTimeout(time.Second) + for !tm.expired() { + stat, err := d.card_acmd(acmdSD_APP_OP_COND, arg) + if err != nil { + return err + } else if stat == 0 { // READY state. + break + } + d.yield() + } + err := d.updateCSDCID() + if err != nil { + return err + } + + if d.kind != TypeSD2 { + return nil // Done if not SD2. + } + + // Discover if card is high capacity. + stat, err := d.card_command(cmdReadOCR, 0) + if err != nil { + return err + } else if stat != 0 { + return makeResponseError(response1(stat)) + } + ocr, err := d.receive() + if err != nil { + return err + } else if ocr&0xc0 == 0xc0 { + d.kind = TypeSDHC + } + // Discard next 3 bytes. + d.receive() + d.receive() + d.receive() + return nil +} + +func (d *SPICard) updateCSDCID() (err error) { + // read CID + d.cid, err = d.read_cid() + if err != nil { + return err + } + d.csd, err = d.read_csd() + if err != nil { + return err + } + blklen := d.csd.ReadBlockLen() + d.blk, err = makeBlockIndexer(int(blklen)) + if err != nil { + return err + } + return nil +} + +// ReadBlock reads to a buffer multiple of 512 bytes from sdcard into dst starting at block `startBlockIdx`. +func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) { + numblocks, err := d.checkBounds(startBlockIdx, len(dst)) + if err != nil { + return 0, err + } + if d.kind != TypeSDHC { + startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards. + } + + d.csEnable(true) + defer d.csEnable(false) + + if numblocks == 1 { + return d.read_block_single(dst, startBlockIdx) + + } else if numblocks > 1 { + // TODO: implement multi block transaction reading. + // Rust code is failing here. + blocksize := int(d.blk.size()) + for i := 0; i < numblocks; i++ { + dataoff := i * blocksize + d.csEnable(true) + _, err := d.read_block_single(dst[dataoff:dataoff+blocksize], int64(i)+startBlockIdx) + if err != nil { + return dataoff, err + } + d.csEnable(false) + } + return len(dst), nil + } + panic("unreachable numblocks<=0") +} + +func (d *SPICard) EraseBlocks(startBlock, numberOfBlocks int64) error { + return errors.New("sd:erase not implemented") +} + +// WriteBlocks writes to sdcard from a buffer multiple of 512 bytes from src starting at block `startBlockIdx`. +func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) (int, error) { + numblocks, err := d.checkBounds(startBlockIdx, len(data)) + if err != nil { + return 0, err + } + if d.kind != TypeSDHC { + startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards. + } + d.csEnable(true) + defer d.csEnable(false) + + writeTimeout := 2 * d.timeout + if numblocks == 1 { + return d.write_block_single(data, startBlockIdx) + + } else if numblocks > 1 { + // Start multi block write. + blocksize := int(d.blk.size()) + _, err = d.card_command(cmdWriteMultipleBlock, uint32(startBlockIdx)) + if err != nil { + return 0, err + } + + for i := 0; i < numblocks; i++ { + offset := i * blocksize + err = d.wait_not_busy(writeTimeout) + if err != nil { + return 0, err + } + err = d.write_data(tokWRITE_MULT, data[offset:offset+blocksize]) + if err != nil { + return 0, err + } + } + // Stop the multi write operation. + err = d.wait_not_busy(writeTimeout) + if err != nil { + return 0, err + } + err = d.send(tokSTOP_TRAN) + if err != nil { + return 0, err + } + _, err = d.card_command(cmdStopTransmission, 0) + if err != nil { + return 0, err + } + return len(data), nil + } + panic("unreachable numblocks<=0") +} + +func (d *SPICard) read_block_single(dst []byte, startBlockIdx int64) (int, error) { + _, err := d.card_command(cmdReadSingleBlock, uint32(startBlockIdx)) + if err != nil { + return 0, err + } + err = d.read_data(dst) + if err != nil { + return 0, err + } + return len(dst), nil +} + +func (d *SPICard) write_block_single(data []byte, startBlockIdx int64) (_ int, err error) { + _, err = d.card_command(cmdWriteBlock, uint32(startBlockIdx)) + if err != nil { + return 0, err + } + err = d.write_data(tokSTART_BLOCK, data) + if err != nil { + return 0, err + } + err = d.wait_not_busy(2 * d.timeout) + if err != nil { + return 0, err + } + status, err := d.card_command(cmdSendStatus, 0) + if err != nil { + return 0, err + } else if status != 0 { + return 0, makeResponseError(response1(status)) + } + status, err = d.receive() + if err != nil { + return 0, err + } else if status != 0 { + return 0, errWrite + } + return len(data), nil +} + +func (d *SPICard) checkBounds(startBlockIdx int64, datalen int) (numblocks int, err error) { + if startBlockIdx >= d.NumberOfBlocks() { + return 0, errOOB + } else if startBlockIdx > math.MaxUint32 { + return 0, errCardNotSupported + } + if d.blk.off(int64(datalen)) > 0 { + return 0, errNeedBlockLenMultiple + } + numblocks = int(d.blk.idx(int64(datalen))) + if numblocks == 0 { + return 0, io.ErrShortBuffer + } + return numblocks, nil +} + +func (d *SPICard) read_cid() (cid CID, err error) { + err = d.cmd_read(cmdSendCID, 0, d.cid.data[:16]) // CMD10. + if err != nil { + return cid, err + } + if !d.cid.IsValid() { + return cid, errBadCSDCID + } + return d.cid, nil +} + +func (d *SPICard) read_csd() (csd CSD, err error) { + err = d.cmd_read(cmdSendCSD, 0, d.csd.data[:16]) // CMD9. + if err != nil { + return csd, err + } + if !d.csd.IsValid() { + return csd, errBadCSDCID + } + return d.csd, nil +} + +func (d *SPICard) cmd_read(cmd command, args uint32, buf []byte) error { + status, err := d.card_command(cmd, args) + if err != nil { + return err + } else if status != 0 { + return makeResponseError(response1(status)) + } + return d.read_data(buf) +} + +func (d *SPICard) card_acmd(acmd appcommand, args uint32) (uint8, error) { + _, err := d.card_command(cmdAppCmd, 0) + if err != nil { + return 0, err + } + return d.card_command(command(acmd), args) +} + +func (d *SPICard) card_command(cmd command, args uint32) (uint8, error) { + const transmitterBit = 1 << 6 + err := d.wait_not_busy(d.timeout) + if err != nil { + return 0, err + } + buf := d.bufcmd[:6] + // Start bit is always zero; transmitter bit is one since we are Host. + + buf[0] = transmitterBit | byte(cmd) + binary.BigEndian.PutUint32(buf[1:5], args) + buf[5] = crc7noshift(buf[:5]) | 1 // CRC and end bit which is always 1. + + err = d.bus.Tx(buf, nil) + if err != nil { + return 0, err + } + if cmd == cmdStopTransmission { + d.receive() // skip stuff byte for stop read. + } + + for i := 0; i < 512; i++ { + result, err := d.receive() + if err != nil { + return 0, err + } + if result&0x80 == 0 { + return result, nil + } + } + return 0, errReadTimeout +} + +func (d *SPICard) read_data(data []byte) (err error) { + var status uint8 + tm := d.timers[1].setTimeout(d.timeout) + for !tm.expired() { + status, err = d.receive() + if err != nil { + return err + } else if status != 0xff { + break + } else if tm.expired() { + return errReadTimeout + } + d.yield() + } + if status != tokSTART_BLOCK { + return errWaitStartBlock + } + err = d.bus.Tx(nil, data) + if err != nil { + return err + } + // CRC16 is always sent on a data block. + crchi, _ := d.receive() + crclo, _ := d.receive() + d.lastCRC = uint16(crclo) | uint16(crchi)<<8 + return nil +} + +func (s *SPICard) wait_not_busy(timeout time.Duration) error { + tm := s.timers[1].setTimeout(timeout) + for { + tok, err := s.receive() + if err != nil { + return err + } else if tok == 0xff { + break + } else if tm.expired() { + return errBusyTimeout + } + s.yield() + } + return nil +} + +func (s *SPICard) write_data(tok byte, data []byte) error { + if len(data) > 512 { + return errors.New("data too long for write_data") + } + crc := CRC16(data) + err := s.send(tok) + if err != nil { + return err + } + err = s.bus.Tx(data, nil) + if err != nil { + return err + } + err = s.send(byte(crc >> 8)) + if err != nil { + return err + } + err = s.send(byte(crc)) + if err != nil { + return err + } + status, err := s.receive() + if err != nil { + return err + } + if status&_DATA_RES_MASK != _DATA_RES_ACCEPTED { + return makeResponseError(response1(status)) + } + return nil +} + +func (s *SPICard) receive() (byte, error) { + return s.bus.Transfer(0xFF) +} + +func (s *SPICard) send(b byte) error { + _, err := s.bus.Transfer(b) + return err +} +func (c *SPICard) csEnable(b bool) { + // SD Card initialization issues with misbehaving SD cards requires clocking the card. + // https://electronics.stackexchange.com/questions/303745/sd-card-initialization-problem-cmd8-wrong-response + c.bus.Transfer(0xff) + c.cs(!b) + c.bus.Transfer(0xff) +}