Skip to content

Commit e082054

Browse files
authored
sd package: sdcard package redesign (#639)
* begin adding sd.Card refactor * CSD logic shared between V1 and V2 * failing CRC7 implementation * passing tests * rename commands * implement waitToken * add status string method * no crc errors in status; closing the gap? * add config baud increase docs * remove some of API * still working on consolidation of init * fully comply * add prints * remove prints * remove unused API * add BlockDevice * implement Card interface * rename EraseBlocks to EraseSectors * add blkIdxer * working BlockDevice * delete rustref.go; add readme to sd * add sd/README.md * sd: backtrack on EraseSectors, stick to block nomenclature * remove remnants of sector size * fix examples/sd/main.go * add documentation, smoke test and fix a couple bugs
1 parent dc6edbb commit e082054

9 files changed

Lines changed: 2008 additions & 0 deletions

File tree

examples/sd/main.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"machine"
6+
"time"
7+
8+
"tinygo.org/x/drivers/sd"
9+
)
10+
11+
const (
12+
SPI_RX_PIN = machine.GP16
13+
SPI_TX_PIN = machine.GP19
14+
SPI_SCK_PIN = machine.GP18
15+
SPI_CS_PIN = machine.GP15
16+
)
17+
18+
var (
19+
spibus = machine.SPI0
20+
spicfg = machine.SPIConfig{
21+
Frequency: 250000,
22+
Mode: 0,
23+
SCK: SPI_SCK_PIN,
24+
SDO: SPI_TX_PIN,
25+
SDI: SPI_RX_PIN,
26+
}
27+
)
28+
29+
func main() {
30+
time.Sleep(time.Second)
31+
SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput})
32+
err := spibus.Configure(spicfg)
33+
if err != nil {
34+
panic(err.Error())
35+
}
36+
sdcard := sd.NewSPICard(spibus, SPI_CS_PIN.Set)
37+
println("start init")
38+
err = sdcard.Init()
39+
if err != nil {
40+
panic("sd card init:" + err.Error())
41+
}
42+
// After initialization it's safe to increase SPI clock speed.
43+
csd := sdcard.CSD()
44+
kbps := csd.TransferSpeed().RateKilobits()
45+
spicfg.Frequency = uint32(kbps * 1000)
46+
err = spibus.Configure(spicfg)
47+
48+
cid := sdcard.CID()
49+
fmt.Printf("name=%s\ncsd=\n%s\n", cid.ProductName(), csd.String())
50+
51+
bd, err := sd.NewBlockDevice(sdcard, csd.ReadBlockLen(), csd.NumberOfBlocks())
52+
if err != nil {
53+
panic("block device creation:" + err.Error())
54+
}
55+
var mc MemChecker
56+
57+
ok, badBlkIdx, err := mc.MemCheck(bd, 2, 100)
58+
if err != nil {
59+
panic("memcheck:" + err.Error())
60+
}
61+
if !ok {
62+
println("bad block", badBlkIdx)
63+
} else {
64+
println("memcheck ok")
65+
}
66+
}
67+
68+
type MemChecker struct {
69+
rdBuf []byte
70+
storeBuf []byte
71+
wrBuf []byte
72+
}
73+
74+
func (mc *MemChecker) MemCheck(bd *sd.BlockDevice, blockIdx, numBlocks int64) (memOK bool, badBlockIdx int64, err error) {
75+
size := bd.BlockSize() * numBlocks
76+
if len(mc.rdBuf) < int(size) {
77+
mc.rdBuf = make([]byte, size)
78+
mc.wrBuf = make([]byte, size)
79+
mc.storeBuf = make([]byte, size)
80+
for i := range mc.wrBuf {
81+
mc.wrBuf[i] = byte(i)
82+
}
83+
}
84+
// Start by storing the original block contents.
85+
_, err = bd.ReadAt(mc.storeBuf, blockIdx)
86+
if err != nil {
87+
return false, blockIdx, err
88+
}
89+
90+
// Write the test pattern.
91+
_, err = bd.WriteAt(mc.wrBuf, blockIdx)
92+
if err != nil {
93+
return false, blockIdx, err
94+
}
95+
// Read back the test pattern.
96+
_, err = bd.ReadAt(mc.rdBuf, blockIdx)
97+
if err != nil {
98+
return false, blockIdx, err
99+
}
100+
for j := 0; j < len(mc.rdBuf); j++ {
101+
// Compare the read back data with the test pattern.
102+
if mc.rdBuf[j] != mc.wrBuf[j] {
103+
badBlock := blockIdx + int64(j)/bd.BlockSize()
104+
return false, badBlock, nil
105+
}
106+
mc.rdBuf[j] = 0
107+
}
108+
// Leave the card in it's previous state.
109+
_, err = bd.WriteAt(mc.storeBuf, blockIdx)
110+
return true, -1, nil
111+
}

sd/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## `sd` package
2+
3+
File map:
4+
* `blockdevice.go`: Contains logic for creating an `io.WriterAt` and `io.ReaderAt` with the `sd.BlockDevice` concrete type
5+
from the `sd.Card` interface which is intrinsically a blocked reader and writer.
6+
7+
* `spicard.go`: Contains the `sd.SpiCard` driver for controlling an SD card over SPI using the most commonly available circuit boards.
8+
9+
* `responses.go`: Contains a currently unused SD response implementations as per the latest specification.
10+
11+
* `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.

sd/blockdevice.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package sd
2+
3+
import (
4+
"errors"
5+
"io"
6+
"math/bits"
7+
)
8+
9+
var (
10+
errNegativeOffset = errors.New("sd: negative offset")
11+
)
12+
13+
// Compile time guarantee of interface implementation.
14+
var _ Card = (*SPICard)(nil)
15+
var _ io.ReaderAt = (*BlockDevice)(nil)
16+
var _ io.WriterAt = (*BlockDevice)(nil)
17+
18+
// Card is the interface implemented by SD card drivers such as [SPICard].
19+
// It provides block-aligned I/O over the card's contents. Use [NewBlockDevice]
20+
// to wrap a Card with byte-addressed [io.ReaderAt] and [io.WriterAt] interfaces.
21+
type Card interface {
22+
// WriteBlocks writes the given data to the card, starting at the given block index.
23+
// The data must be a multiple of the block size.
24+
WriteBlocks(data []byte, startBlockIdx int64) (int, error)
25+
// ReadBlocks reads the given number of blocks from the card, starting at the given block index.
26+
// The dst buffer must be a multiple of the block size.
27+
ReadBlocks(dst []byte, startBlockIdx int64) (int, error)
28+
// EraseBlocks erases blocks starting at startBlockIdx to startBlockIdx+numBlocks.
29+
EraseBlocks(startBlock, numBlocks int64) error
30+
}
31+
32+
// NewBlockDevice creates a new [BlockDevice] from a Card. blockSize must be a
33+
// power of 2. For an initialized [SPICard], blockSize is typically the CSD's
34+
// [CSD.ReadBlockLen] and numBlocks is [SPICard.NumberOfBlocks].
35+
func NewBlockDevice(card Card, blockSize int, numBlocks int64) (*BlockDevice, error) {
36+
if card == nil || blockSize <= 0 || numBlocks <= 0 {
37+
return nil, errors.New("invalid argument(s)")
38+
}
39+
blk, err := makeBlockIndexer(blockSize)
40+
if err != nil {
41+
return nil, err
42+
}
43+
bd := &BlockDevice{
44+
card: card,
45+
blockbuf: make([]byte, blockSize),
46+
blk: blk,
47+
numblocks: int64(numBlocks),
48+
}
49+
return bd, nil
50+
}
51+
52+
// BlockDevice implements the tinyfs.BlockDevice interface for a [Card],
53+
// providing byte-addressed reads and writes at arbitrary offsets by buffering
54+
// non-block-aligned accesses through an internal single-block buffer.
55+
// BlockDevice is not safe for concurrent use.
56+
type BlockDevice struct {
57+
card Card
58+
blockbuf []byte
59+
blk blkIdxer
60+
numblocks int64
61+
}
62+
63+
// ReadAt implements the [io.ReaderAt] interface for an SD card.
64+
// Reads need not be aligned to block boundaries.
65+
func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
66+
if off < 0 {
67+
return 0, errNegativeOffset
68+
}
69+
70+
blockIdx := bd.blk.idx(off)
71+
blockOff := bd.blk.off(off)
72+
if blockOff != 0 {
73+
// Non-aligned first block case.
74+
if _, err = bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
75+
return n, err
76+
}
77+
n += copy(p, bd.blockbuf[blockOff:])
78+
p = p[n:]
79+
blockIdx++
80+
}
81+
82+
fullBlocksToRead := bd.blk.idx(int64(len(p)))
83+
if fullBlocksToRead > 0 {
84+
// 1 or more full blocks case.
85+
endOffset := fullBlocksToRead * bd.blk.size()
86+
ngot, err := bd.card.ReadBlocks(p[:endOffset], blockIdx)
87+
if err != nil {
88+
return n + ngot, err
89+
}
90+
p = p[endOffset:]
91+
n += ngot
92+
blockIdx += fullBlocksToRead
93+
}
94+
95+
if len(p) > 0 {
96+
// Non-aligned last block case.
97+
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
98+
return n, err
99+
}
100+
n += copy(p, bd.blockbuf)
101+
}
102+
return n, nil
103+
}
104+
105+
// WriteAt implements the [io.WriterAt] interface for an SD card. Writes need
106+
// not be aligned to block boundaries: partial blocks are read, modified and
107+
// written back.
108+
func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
109+
if off < 0 {
110+
return 0, errNegativeOffset
111+
}
112+
113+
blockIdx := bd.blk.idx(off)
114+
blockOff := bd.blk.off(off)
115+
if blockOff != 0 {
116+
// Non-aligned first block case.
117+
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
118+
return n, err
119+
}
120+
nexpect := copy(bd.blockbuf[blockOff:], p)
121+
ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx)
122+
if err != nil {
123+
return n, err
124+
} else if ngot != len(bd.blockbuf) {
125+
return n, io.ErrShortWrite
126+
}
127+
n += nexpect
128+
p = p[nexpect:]
129+
blockIdx++
130+
}
131+
132+
fullBlocksToWrite := bd.blk.idx(int64(len(p)))
133+
if fullBlocksToWrite > 0 {
134+
// 1 or more full blocks case.
135+
endOffset := fullBlocksToWrite * bd.blk.size()
136+
ngot, err := bd.card.WriteBlocks(p[:endOffset], blockIdx)
137+
n += ngot
138+
if err != nil {
139+
return n, err
140+
} else if ngot != int(endOffset) {
141+
return n, io.ErrShortWrite
142+
}
143+
p = p[ngot:]
144+
blockIdx += fullBlocksToWrite
145+
}
146+
147+
if len(p) > 0 {
148+
// Non-aligned last block case.
149+
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
150+
return n, err
151+
}
152+
copy(bd.blockbuf, p)
153+
ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx)
154+
if err != nil {
155+
return n, err
156+
} else if ngot != len(bd.blockbuf) {
157+
return n, io.ErrShortWrite
158+
}
159+
n += len(p)
160+
}
161+
return n, nil
162+
}
163+
164+
// Size returns the number of bytes in this block device.
165+
func (bd *BlockDevice) Size() int64 {
166+
return bd.BlockSize() * bd.numblocks
167+
}
168+
169+
// BlockSize returns the size of a block in bytes.
170+
func (bd *BlockDevice) BlockSize() int64 {
171+
return bd.blk.size()
172+
}
173+
174+
// EraseBlocks erases the given number of blocks. An implementation may
175+
// transparently coalesce ranges of blocks into larger bundles if the chip
176+
// supports this. The start and len parameters are in block numbers, use
177+
// EraseBlockSize to map addresses to blocks.
178+
func (bd *BlockDevice) EraseBlocks(startEraseBlockIdx, len int64) error {
179+
return bd.card.EraseBlocks(startEraseBlockIdx, len)
180+
}
181+
182+
// blkIdxer is a helper for calculating block indices and offsets.
183+
type blkIdxer struct {
184+
blockshift int64
185+
blockmask int64
186+
}
187+
188+
// makeBlockIndexer returns a blkIdxer for the given block size,
189+
// which must be a power of 2.
190+
func makeBlockIndexer(blockSize int) (blkIdxer, error) {
191+
if blockSize <= 0 {
192+
return blkIdxer{}, errNoblocks
193+
}
194+
tz := bits.TrailingZeros(uint(blockSize))
195+
if blockSize>>tz != 1 {
196+
return blkIdxer{}, errors.New("blockSize must be a power of 2")
197+
}
198+
blk := blkIdxer{
199+
blockshift: int64(tz),
200+
blockmask: (1 << tz) - 1,
201+
}
202+
return blk, nil
203+
}
204+
205+
// size returns the size of a block in bytes.
206+
func (blk *blkIdxer) size() int64 {
207+
return 1 << blk.blockshift
208+
}
209+
210+
// off gets the offset of the byte at byteIdx from the start of its block.
211+
//
212+
//go:inline
213+
func (blk *blkIdxer) off(byteIdx int64) int64 {
214+
return blk._moduloBlockSize(byteIdx)
215+
}
216+
217+
// idx gets the block index that contains the byte at byteIdx.
218+
//
219+
//go:inline
220+
func (blk *blkIdxer) idx(byteIdx int64) int64 {
221+
return blk._divideBlockSize(byteIdx)
222+
}
223+
224+
// modulo and divide are defined in terms of bit operations for speed since
225+
// blockSize is a power of 2.
226+
227+
//go:inline
228+
func (blk *blkIdxer) _moduloBlockSize(n int64) int64 { return n & blk.blockmask }
229+
230+
//go:inline
231+
func (blk *blkIdxer) _divideBlockSize(n int64) int64 { return n >> blk.blockshift }

0 commit comments

Comments
 (0)