-
-
Notifications
You must be signed in to change notification settings - Fork 247
sd package: sdcard package redesign
#639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
soypat
wants to merge
27
commits into
dev
Choose a base branch
from
sdcard-refactor
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
5b65713
begin adding sd.Card refactor
soypat 2f85c8b
CSD logic shared between V1 and V2
soypat 46cd569
failing CRC7 implementation
soypat 3d49155
passing tests
soypat 222f368
rename commands
soypat 66da442
implement waitToken
soypat ace4a89
add status string method
soypat d5db138
no crc errors in status; closing the gap?
soypat 97ef498
add config baud increase docs
soypat 845bd6f
remove some of API
soypat 7db9e9d
still working on consolidation of init
soypat 1b726ef
fully comply
soypat b31c5ca
add prints
soypat 677f8ed
remove prints
soypat 45e207f
remove unused API
soypat e6907db
add BlockDevice
soypat c5ff13a
implement Card interface
soypat cb2ca23
rename EraseBlocks to EraseSectors
soypat 0d80962
add blkIdxer
soypat 9643640
working BlockDevice
soypat 4d0d8e9
delete rustref.go; add readme to sd
soypat e14fbf6
add sd/README.md
soypat 8de5ab7
sd: backtrack on EraseSectors, stick to block nomenclature
soypat 37ae0ad
remove remnants of sector size
soypat 62f5144
fix examples/sd/main.go
soypat 0bc660e
add documentation, smoke test and fix a couple bugs
soypat 46bde26
Merge branch 'dev' into sdcard-refactor
soypat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No bounds checking against device size in this function either. |
||
| 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 } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No bounds checking against device size in this function.