Compare commits

...

27 Commits

Author SHA1 Message Date
Pat Whittingslow 46bde26249 Merge branch 'dev' into sdcard-refactor 2026-07-14 16:54:29 -03:00
Patricio Whittingslow 0bc660e1bc add documentation, smoke test and fix a couple bugs 2026-07-14 16:53:11 -03:00
soypat 62f51445b6 fix examples/sd/main.go 2024-01-23 20:38:30 -03:00
soypat 37ae0ad5b8 remove remnants of sector size 2024-01-23 20:33:05 -03:00
soypat 8de5ab7c64 sd: backtrack on EraseSectors, stick to block nomenclature 2024-01-23 20:25:58 -03:00
soypat e14fbf6d3d add sd/README.md 2024-01-23 20:21:27 -03:00
soypat 4d0d8e9c14 delete rustref.go; add readme to sd 2024-01-23 20:16:08 -03:00
soypat 964364005d working BlockDevice 2024-01-16 00:46:50 -03:00
soypat 0d80962dc8 add blkIdxer 2024-01-15 23:23:08 -03:00
soypat cb2ca239f0 rename EraseBlocks to EraseSectors 2024-01-15 21:25:22 -03:00
soypat c5ff13ad23 implement Card interface 2024-01-15 21:11:49 -03:00
soypat e6907db19e add BlockDevice 2024-01-15 20:09:27 -03:00
soypat 45e207fe2e remove unused API 2024-01-15 01:51:22 -03:00
soypat 677f8ed297 remove prints 2024-01-15 01:33:38 -03:00
soypat b31c5ca9c9 add prints 2024-01-15 01:32:09 -03:00
soypat 1b726ef2bd fully comply 2024-01-15 01:31:03 -03:00
soypat 7db9e9d6db still working on consolidation of init 2024-01-15 01:04:39 -03:00
soypat 845bd6fe93 remove some of API 2024-01-14 22:50:42 -03:00
soypat 97ef4986ba add config baud increase docs 2024-01-14 20:16:45 -03:00
soypat d5db138d9a no crc errors in status; closing the gap? 2024-01-14 20:08:57 -03:00
soypat ace4a8924b add status string method 2024-01-14 18:59:28 -03:00
soypat 66da4422dc implement waitToken 2024-01-14 17:38:20 -03:00
soypat 222f368681 rename commands 2024-01-14 17:04:16 -03:00
soypat 3d491553dd passing tests 2024-01-14 16:10:31 -03:00
soypat 46cd56951c failing CRC7 implementation 2024-01-14 14:17:04 -03:00
soypat 2f85c8bd04 CSD logic shared between V1 and V2 2024-01-14 12:39:10 -03:00
soypat 5b6571350d begin adding sd.Card refactor 2024-01-14 02:31:41 -03:00
9 changed files with 2008 additions and 0 deletions
+111
View File
@@ -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
}
+11
View File
@@ -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.
+231
View File
@@ -0,0 +1,231 @@
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)
// Card is the interface implemented by SD card drivers such as [SPICard].
// It provides block-aligned I/O over the card's contents. Use [NewBlockDevice]
// to wrap a Card with byte-addressed [io.ReaderAt] and [io.WriterAt] interfaces.
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. blockSize must be a
// power of 2. For an initialized [SPICard], blockSize is typically the CSD's
// [CSD.ReadBlockLen] and numBlocks is [SPICard.NumberOfBlocks].
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 the tinyfs.BlockDevice interface for a [Card],
// providing byte-addressed reads and writes at arbitrary offsets by buffering
// non-block-aligned accesses through an internal single-block buffer.
// BlockDevice is not safe for concurrent use.
type BlockDevice struct {
card Card
blockbuf []byte
blk blkIdxer
numblocks int64
}
// ReadAt implements the [io.ReaderAt] interface for an SD card.
// Reads need not be aligned to block boundaries.
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 the [io.WriterAt] interface for an SD card. Writes need
// not be aligned to block boundaries: partial blocks are read, modified and
// written back.
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
}
// makeBlockIndexer returns a blkIdxer for the given block size,
// which must be a power of 2.
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 }
+178
View File
@@ -0,0 +1,178 @@
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)
}
}
}
// TestCSDv2Capacity checks CSDv2 C_SIZE decoding and the capacity formula.
//
// Field layout and formula are from the SD Physical Layer Simplified
// Specification Version 9.10, section 5.3.3 "CSD Register (CSD Version 2.0)":
// C_SIZE occupies CSD bits [69:48] and user memory capacity is
//
// memory capacity = (C_SIZE+1) * 512KByte (512KByte = 524288 bytes)
//
// Specification download: https://www.sdcard.org/downloads/pls/
//
// Regression test for two past bugs in CSDv2:
// - csize() read byte 7 as data[7]>>2 instead of data[7]&0x3F, dropping
// C_SIZE bits [17:16] (misdecoded cards > 32GiB).
// - DeviceCapacity() computed csize*512000 instead of (csize+1)*524288.
func TestCSDv2Capacity(t *testing.T) {
tests := []struct {
name string
csd []byte
wantCSize uint32
wantCap int64
}{
{
// Same 8GB-card register as in TestCRC7 above, CRC byte appended
// (CRC7=0b1110010 per that test, stored as crc<<1|always1).
// C_SIZE = 0x003C01 = 15361 -> 15362 * 524288 = 8054112256 bytes.
name: "8GB card (in-repo vector)",
csd: []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0, 0b1110010<<1 | 1},
wantCSize: 0x003C01,
wantCap: 8054112256,
},
// {
// name: "8GB card",
// csd: csdv2Bytes(0x003C01),
// wantCSize: 0x003FFF,
// wantCap: 2 << 40,
// },
{
// Synthetic: C_SIZE = 0x01FFFF -> 131072 * 524288 = 64GiB.
// Exercises C_SIZE bits [17:16], stored in CSD byte 7 (CSD bits [65:64]).
name: "64GiB synthetic",
csd: csdv2Bytes(0x01FFFF),
wantCSize: 0x01FFFF,
wantCap: 64 << 30,
},
{
// Synthetic: maximum v2 C_SIZE = 0x3FFFFF -> 4194304 * 524288 = 2TiB,
// the SDXC upper bound per section 5.3.3.
name: "2TiB synthetic (max C_SIZE)",
csd: csdv2Bytes(0x3FFFFF),
wantCSize: 0x3FFFFF,
wantCap: 2 << 40,
},
}
for _, tt := range tests {
csd, err := DecodeCSD(tt.csd)
if err != nil {
t.Fatalf("%s: DecodeCSD: %v", tt.name, err)
}
v2 := csd.MustV2()
if got := v2.csize(); got != tt.wantCSize {
t.Errorf("%s: csize() = %#x, want %#x", tt.name, got, tt.wantCSize)
}
if got := csd.DeviceCapacity(); got != tt.wantCap {
t.Errorf("%s: DeviceCapacity() = %d, want %d", tt.name, got, tt.wantCap)
}
wantBlocks := tt.wantCap / int64(csd.ReadBlockLen())
if got := csd.NumberOfBlocks(); got != wantBlocks {
t.Errorf("%s: NumberOfBlocks() = %d, want %d", tt.name, got, wantBlocks)
}
}
}
// csdv2Bytes returns a 16-byte CSD v2 register with csize spliced into
// CSD bits [69:48] and a freshly computed CRC7+always1 last byte.
// Non-capacity fields are copied from the 8GB-card vector above.
func csdv2Bytes(csize uint32) []byte {
b := []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0}
b[7] = byte(csize >> 16 & 0x3F)
b[8] = byte(csize >> 8)
b[9] = byte(csize)
return append(b, crc7noshift(b)|1)
}
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.
}
+585
View File
@@ -0,0 +1,585 @@
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 "<unsupported CSD version>"
}
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 = "<invalid format>"
}
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,
}
+8
View File
@@ -0,0 +1,8 @@
// Package sd implements SD card drivers and the SD card specification:
// CID and CSD register decoding, command definitions and CRC7/CRC16 checksums.
//
// The [SPICard] type drives an SD card over a SPI bus and implements the
// [Card] interface, which exposes block-aligned I/O. [BlockDevice] wraps any
// [Card] with byte-addressed [io.ReaderAt]/[io.WriterAt] implementations
// suitable for filesystem libraries such as tinyfs.
package sd
+325
View File
@@ -0,0 +1,325 @@
package sd
import (
"encoding/binary"
)
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
)
// response1 is the R1 response token returned by the card in SPI mode
// after every command; a bitfield of error and idle-state flags.
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 }
// response1Err wraps a non-zero response1 status as an error.
type response1Err struct {
context string
status response1
}
func (e response1Err) Error() string {
if e.context != "" {
return "sd:" + e.context + " " + e.status.Response()
}
return e.status.Response()
}
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
)
// state is the card state machine state as encoded in the
// CURRENT_STATE bits of the Card Status register (section 4.10.1).
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 48-bit response to a command in SD-bus mode,
// as per section 4.9.1. It carries the 32-bit Card Status register.
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()
}
// r6 is the 48-bit Published RCA response, as per section 4.9.5. It carries
// the card's new Relative Card Address and a subset of the Card Status bits.
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<<bit) != 0 {
b = append(b, status(bit).string()...)
b = append(b, delim)
}
}
b = append(b[:len(b)-1], ']')
return b
}
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[stateIdle-0]
_ = x[stateReady-1]
_ = x[stateIdent-2]
_ = x[stateStby-3]
_ = x[stateTran-4]
_ = x[stateData-5]
_ = x[stateRcv-6]
_ = x[statePrg-7]
_ = x[stateDis-8]
}
const _state_name = "IdleReadyIdentStbyTranDataRcvPrgDis"
var _state_index = [...]uint8{0, 4, 9, 14, 18, 22, 26, 29, 32, 35}
func (i state) String() string {
if i >= state(len(_state_index)-1) {
return "<reserved state>"
}
return _state_name[_state_index[i]:_state_index[i+1]]
}
+558
View File
@@ -0,0 +1,558 @@
package sd
import (
"encoding/binary"
"errors"
"io"
"math"
"time"
"tinygo.org/x/drivers"
)
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")
)
// digitalPinout sets the logic level of an output pin; true for high, false for low.
type digitalPinout = func(b bool)
// SPICard is a SPI-mode SD card driver. It implements the [Card] interface
// and is initialized with [NewSPICard] followed by a call to [SPICard.Init].
// SPICard is not safe for concurrent use.
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
}
// NewSPICard returns a new [SPICard] that communicates over spi using cs as
// the chip select pin. The returned card must be initialized with [SPICard.Init]
// before use.
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()
}
// NumberOfBlocks returns the number of readable and writable blocks on the card,
// as calculated from the CSD read during [SPICard.Init].
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
}
// ReadBlocks reads card data into dst beginning at the block index startBlockIdx.
// len(dst) must be a multiple of the card's block size (see [CSD.ReadBlockLen]).
// It returns the number of bytes read into dst and any error encountered.
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")
}
// EraseBlocks erases numberOfBlocks blocks beginning at startBlock.
// It always returns an error since erase is not yet implemented for SPICard.
func (d *SPICard) EraseBlocks(startBlock, numberOfBlocks int64) error {
return errors.New("sd:erase not implemented")
}
// WriteBlocks writes data to the card beginning at the block index startBlockIdx.
// len(data) must be a multiple of the card's block size (see [CSD.WriteBlockLen]).
// It returns the number of bytes written and any error encountered.
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)
}
+1
View File
@@ -151,6 +151,7 @@ tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/w5500/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sd
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)