From 5b6571350d8f2cbdae28eb7c128d1141b3e5794e Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 02:31:41 -0300 Subject: [PATCH 01/25] begin adding sd.Card refactor --- examples/sd/main.go | 54 ++++++ sd/card.go | 409 ++++++++++++++++++++++++++++++++++++++++++++ sd/card_test.go | 45 +++++ sd/definitions.go | 300 ++++++++++++++++++++++++++++++++ sd/timer.go | 22 +++ 5 files changed, 830 insertions(+) create mode 100644 examples/sd/main.go create mode 100644 sd/card.go create mode 100644 sd/card_test.go create mode 100644 sd/definitions.go create mode 100644 sd/timer.go diff --git a/examples/sd/main.go b/examples/sd/main.go new file mode 100644 index 000000000..440180c83 --- /dev/null +++ b/examples/sd/main.go @@ -0,0 +1,54 @@ +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 +) + +func main() { + time.Sleep(time.Second) + SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + err := spibus.Configure(machine.SPIConfig{ + Frequency: 250000, + Mode: 0, + SCK: SPI_SCK_PIN, + SDO: SPI_TX_PIN, + SDI: SPI_RX_PIN, + }) + if err != nil { + panic(err.Error()) + } + sdcard := sd.NewCard(spibus, SPI_CS_PIN.Set) + + err = sdcard.Init() + if err != nil { + panic(err.Error()) + } + csd := sdcard.CSD() + fmt.Printf("cid=%+v\ncsd=%+v\ncsdfmt=\n%s\ndone!", sdcard.CID(), csd, csd.String()) + + var buf [512]byte + for i := 1; i < 11; i += 1 { + time.Sleep(time.Millisecond) + err = sdcard.ReadBlock(uint32(i), buf[:]) + if err != nil { + println("err reading block", i, ":", err.Error()) + continue + } + fmt.Printf("block %d crc=%#x:\n\t%#x\n", i, sdcard.LastReadCRC(), buf[:]) + } +} diff --git a/sd/card.go b/sd/card.go new file mode 100644 index 000000000..190fb83c4 --- /dev/null +++ b/sd/card.go @@ -0,0 +1,409 @@ +package sd + +import ( + "encoding/binary" + "errors" + "runtime" + "strconv" + "time" + + "tinygo.org/x/drivers" +) + +var ( + errNoSDCard = errors.New("sd:no card") + errCardNotSupported = errors.New("sd:card not supported") + errCmd8 = errors.New("sd:cmd8") + errCmdOCR = errors.New("sd:cmd_ocr") + errCmdBlkLen = errors.New("sd:cmd_blklen") + errAcmdAppCond = errors.New("sd:acmd_appOrCond") + errWaitStartBlock = errors.New("sd:wait start block") + errNeed512 = errors.New("sd:need 512 bytes for I/O") + errWrite = errors.New("sd:write") + errWriteTimeout = errors.New("sd:write timeout") +) + +type digitalPinout func(b bool) + +type Card struct { + bus drivers.SPI + cs digitalPinout + bufcmd [6]byte + buf [512]byte + bufTok [1]byte + kind CardKind + cid CID + csd CSD + lastCRC uint16 +} + +func NewCard(spi drivers.SPI, cs digitalPinout) *Card { + return &Card{bus: spi, cs: cs} +} + +func (c *Card) csEnable(b bool) { c.cs(!b) } + +// LastReadCRC returns the CRC for the last ReadBlock operation. +func (c *Card) LastReadCRC() uint16 { return c.lastCRC } + +func (d *Card) Init() error { + dummy := d.buf[:] + for i := range dummy { + dummy[i] = 0xFF + } + defer d.csEnable(false) + + d.csEnable(true) + // clock card at least 100 cycles with cs high + d.bus.Tx(dummy[:10], nil) + d.csEnable(false) + + d.bus.Tx(dummy[:], nil) + + // CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts) + ok := false + tm := setTimeout(0, 2*time.Second) + for !tm.expired() { + // Wait up to 2 seconds to be the same as the Arduino + result, err := d.cmd(CMD0_GO_IDLE_STATE, 0, 0x95) + if err != nil { + return err + } + if result == _R1_IDLE_STATE { + ok = true + break + } + } + if !ok { + return errNoSDCard + } + + // CMD8: determine card version + r1, err := d.cmd(CMD8_SEND_IF_COND, 0x01AA, 0x87) + if err != nil { + return err + } + if r1.IllegalCmdError() { + d.kind = TypeSD1 + return errCardNotSupported + } else { + // r7 response + status := byte(0) + for i := 0; i < 3; i++ { + var err error + status, err = d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + } + if (status & 0x0F) != 0x01 { + return makeResponseError(response1(status)) + } + + for i := 3; i < 4; i++ { + var err error + status, err = d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + } + if status != 0xAA { + return makeResponseError(response1(status)) + } + d.kind = TypeSD2 + } + + // initialize card and send host supports SDHC if SD2 + arg := uint32(0) + if d.kind == TypeSD2 { + arg = 0x40000000 + } + + // check for timeout + ok = false + tm = setTimeout(0, 2*time.Second) + for !tm.expired() { + r1, err = d.appCmd(ACMD41_SD_APP_OP_COND, arg) + if err != nil { + return err + } + if r1 == 0 { + break + } + } + if r1 != 0 { + return makeResponseError(r1) + } + + // if SD2 read OCR register to check for SDHC card + if d.kind == TypeSD2 { + err := d.cmdEnsure0Status(CMD58_READ_OCR, 0, 0xFF) + if err != nil { + return err + } + + statusb, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + if (statusb & 0xC0) == 0xC0 { + d.kind = TypeSDHC + } + // discard rest of ocr - contains allowed voltage range + for i := 1; i < 4; i++ { + d.bus.Transfer(byte(0xFF)) + } + } + err = d.cmdEnsure0Status(CMD16_SET_BLOCKLEN, 0x0200, 0xff) + if err != nil { + return err + } + + // read CID + d.cid, err = d.readCID() + if err != nil { + return err + } + d.csd, err = d.readCSD() + if err != nil { + return err + } + return nil +} + +// ReadData reads 512 bytes from sdcard into dst. +func (d *Card) ReadBlock(block uint32, dst []byte) error { + if len(dst) != 512 { + return errNeed512 + } + + // use address if not SDHC card + if d.kind != TypeSDHC { + block <<= 9 + } + err := d.cmdEnsure0Status(CMD17_READ_SINGLE_BLOCK, block, 0xFF) + if err != nil { + return err + } + defer d.csEnable(false) + + if err := d.waitStartBlock(); err != nil { + return err + } + buf := d.buf[:] + err = d.bus.Tx(buf, dst) + if err != nil { + return err + } + + // skip CRC (2byte) + hi, _ := d.bus.Transfer(byte(0xFF)) + lo, _ := d.bus.Transfer(byte(0xFF)) + d.lastCRC = uint16(hi)<<8 | uint16(lo) + return nil +} + +// WriteBlock writes 512 bytes from dst to sdcard. +func (d *Card) WriteBlock(block uint32, src []byte) error { + if len(src) != 512 { + return errNeed512 + } + + // use address if not SDHC card + if d.kind != TypeSDHC { + block <<= 9 + } + err := d.cmdEnsure0Status(CMD24_WRITE_BLOCK, block, 0xFF) + if err != nil { + return err + } + defer d.csEnable(false) + // wait 1 byte? + token := byte(0xFE) + d.bus.Transfer(token) + + err = d.bus.Tx(src[:512], nil) + if err != nil { + return err + } + + // send dummy CRC (2 byte) + d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(byte(0xFF)) + + // Data Resp. + r, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + if (r & 0x1F) != 0x05 { + return errWrite + } + + // wait no busy + err = d.waitNotBusy(600 * time.Millisecond) + if err != nil { + return errWriteTimeout + } + + return nil +} + +// CID returns a copy of the Card Identification Register value last read. +func (d *Card) CID() CID { return d.cid } + +// CSD returns a copy of the Card Specific Data Register value last read. +func (d *Card) CSD() CSD { return d.csd } + +func (d *Card) readCID() (CID, error) { + buf := d.buf[len(d.buf)-16:] + if err := d.readRegister(CMD10_SEND_CID, buf); err != nil { + return CID{}, err + } + return DecodeCID(buf) +} + +func (d *Card) readCSD() (CSD, error) { + buf := d.buf[len(d.buf)-16:] + if err := d.readRegister(CMD9_SEND_CSD, buf); err != nil { + return CSD{}, err + } + return DecodeCSD(buf) +} + +func (d *Card) readRegister(cmd uint8, dst []byte) error { + err := d.cmdEnsure0Status(cmd, 0, 0xFF) + if err != nil { + return err + } + if err := d.waitStartBlock(); err != nil { + return err + } + // transfer data + for i := uint16(0); i < 16; i++ { + r, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + dst[i] = r + } + // skip CRC. + d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(byte(0xFF)) + d.csEnable(false) + return nil +} + +func (d *Card) appCmd(cmd byte, arg uint32) (response1, error) { + status, err := d.cmd(CMD55_APP_CMD, 0, 0xFF) + if err != nil { + return status, err + } + return d.cmd(cmd, arg, 0xFF) +} + +func (d *Card) cmdEnsure0Status(cmd byte, arg uint32, crc byte) error { + status, err := d.cmd(cmd, arg, crc) + if err != nil { + return err + } + if status != 0 { + return makeResponseError(status) + } + return nil +} + +func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { + d.csEnable(true) + + if cmd != 12 { + d.waitNotBusy(300 * time.Millisecond) + } + + // create and send the command + buf := d.bufcmd[:] + buf[0] = 0x40 | cmd + binary.BigEndian.PutUint32(buf[1:5], arg) + buf[5] = crc + d.bus.Tx(buf, nil) + + if cmd == 12 { + // skip 1 byte + d.bus.Transfer(byte(0xFF)) + } + + // wait for the response (response[7] == 0) + d.buf[0] = 0xFF + dummy := d.buf[:1] + for i := 0; i < 0xFFFF; i++ { + d.bus.Tx(dummy, d.bufTok[:]) + response := response1(d.bufTok[0]) + if (response & 0x80) == 0 { + return response, nil + } + } + + // TODO + //// timeout + d.csEnable(false) + d.bus.Transfer(byte(0xFF)) + + return 0xFF, nil // -1 +} + +func (d *Card) waitNotBusy(timeout time.Duration) error { + tm := setTimeout(1, timeout) + for !tm.expired() { + r, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + if r == 0xFF { + return nil + } + runtime.Gosched() + } + return nil +} + +func (d *Card) waitStartBlock() error { + status := byte(0xFF) + + tm := setTimeout(0, 300*time.Millisecond) + for !tm.expired() { + var err error + status, err = d.bus.Transfer(byte(0xFF)) + if err != nil { + d.csEnable(false) + return err + } + if status != 0xFF { + break + } + } + + if status != 254 { + d.csEnable(false) + return errWaitStartBlock + } + + return nil +} + +type response1Err struct { + context string + status response1 +} + +func (e response1Err) Error() string { + if e.context != "" { + return "sd:" + e.context + " " + strconv.Itoa(int(e.status)) + } + return "sd:status " + strconv.Itoa(int(e.status)) +} + +func makeResponseError(status response1) error { + return response1Err{ + status: status, + } +} diff --git a/sd/card_test.go b/sd/card_test.go new file mode 100644 index 000000000..91fa1293e --- /dev/null +++ b/sd/card_test.go @@ -0,0 +1,45 @@ +package sd + +import ( + "encoding/hex" + "testing" +) + +func TestCRC(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 CRC16(buf []byte) (sum uint16) { + const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1 + var crc uint16 = 0x0000 // Initial value + 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 +} diff --git a/sd/definitions.go b/sd/definitions.go new file mode 100644 index 000000000..433d8bfcb --- /dev/null +++ b/sd/definitions.go @@ -0,0 +1,300 @@ +package sd + +import ( + "bytes" + "encoding/binary" + "io" + "strconv" + "time" +) + +type CardKind uint8 + +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 { + ManufacturerID uint8 // 0:1 + OEMApplicationID uint16 // 1:3 + prodName [5]byte // 3:8 + // productRevision n.m + productRev byte // 8:9 + ProductSerialNumber uint32 // 9:13 + // Manufacturing date bitfield: + // - yearhi=0:4 + // - reserved=4:8 + // - month=8:12 + // - yearlo=12:16 + date [2]byte // 13:15 +} + +func DecodeCID(b []byte) (CID, error) { + if len(b) < 16 { + return CID{}, io.ErrShortBuffer + } + cid := CID{ + ManufacturerID: b[0], + OEMApplicationID: binary.BigEndian.Uint16(b[1:3]), + prodName: [5]byte{b[3], b[4], b[5], b[6], b[7]}, + productRev: b[8], + ProductSerialNumber: binary.BigEndian.Uint32(b[9:13]), + date: [2]byte{b[13], b[14]}, + } + + return cid, nil +} + +func (c *CID) ProductName() []byte { + return upToNull(c.prodName[:]) +} + +func (c *CID) ProductRevision() (n, m uint8) { + return c.productRev >> 4, c.productRev & 0x0F +} + +/* +CSD Register Fields: +Name: Field: Width: Value: CSD-Slice: +CSD Structure CSD_STRUCTURE 2 00b R[127:128] +TAAC TAAC 8 00h R[119:113] +NSAC NSAC 8 00h R[111:105] +TRAN_SPEED TRAN_SPEED 8 32h or 5Ah R[103:97] +CCC CCC 12 01x110110101b R[95:85] +READ_BL_LEN READ_BL_LEN 4 xh R[83:81] +READ_BL_PARTIAL READ_BL_PARTIAL 1 1b R[79:80] +WRITE_BLK_MISALIGN WRITE_BLK_MISALIGN 1 xb R[78:79] +READ_BLK_MISALIGN READ_BLK_MISALIGN 1 xb R[77:78] +DSR_IMP DSR_IMP 1 xb R[76:77] +C_SIZE C_SIZE 12 xxxh R[73:63] +VDD_R_CURR_MIN VDD_R_CURR_MIN 3 xxxb R[61:60] +VDD_R_CURR_MAX VDD_R_CURR_MAX 3 xxxb R[58:57] +VDD_W_CURR_MIN VDD_W_CURR_MIN 3 xxxb R[55:54] +VDD_W_CURR_MAX VDD_W_CURR_MAX 3 xxxb R[52:51] +C_SIZE_MULT C_SIZE_MULT 3 xxxb R[49:48] +ERASE_BLK_EN ERASE_BLK_EN 1 xb R[46:47] +SECTOR_SIZE SECTOR_SIZE 7 xxxxxxxb R[45:40] +WP_GRP_SIZE WP_GRP_SIZE 7 xxxxxxxb R[38:33] +WP_GRP_ENABLE WP_GRP_ENABLE 1 xb R[31:32] +R2W_FACTOR R2W_FACTOR 2 xxxb R[28:27] +WRITE_BL_LEN WRITE_BL_LEN 4 xxxxb R[25:23] +WRITE_BL_PARTIAL WRITE_BL_PARTIAL 1 xb R[21:22] +FILE_FORMAT_GRP FILE_FORMAT_GRP 1 xb R[15:16] +COPY COPY 1 xb R[14:15] +PERM_WRITE_PROTECT PERM_WRITE_PROTECT 1 xb R[13:14] +TMP_WRITE_PROTECT TMP_WRITE_PROTECT 1 xb R[12:13] +FILE_FORMAT FILE_FORMAT 2 xxb R[11:11] +CRC CRC 7 xxxxxxxb R[7:2] +Not Used - 1 1b R[0:1] + +Note: 'R' indicates read-only fields, 'R/W' indicates read/write fields. +The values in the 'CSD-Slice' column indicate the bit positions in the CSD register. +*/ + +// 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. +type CSD struct { + data [16]byte +} + +type CSDv1 struct { + CSD +} + +func DecodeCSD(b []byte) (CSD, error) { + if len(b) < 16 { + return CSD{}, io.ErrShortBuffer + } + csd := CSD{} + copy(csd.data[:], b) + return csd, nil +} + +// CSDStructure returns the version of the CSD structure. +func (c *CSD) CSDStructure() uint8 { return c.data[0] >> 6 } + +func (c CSD) MustV1() CSDv1 { + if c.CSDStructure() != 1 { + panic("CSD is not version 1") + } + return CSDv1{CSD: c} +} + +// 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() uint8 { return c.data[2] } + +// TransferSpeed returns the Max Data Transfer Rate. Either 0x32 or 0x5A. +func (c *CSD) TransferSpeed() TransferSpeed { return TransferSpeed(c.data[3]) } + +// CCC returns the Card Command Classes. +func (c *CSD) CCC() uint16 { + return 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() uint16 { return 1 << (c.data[5] & 0x0F) } + +func (c *CSD) ReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 } +func (c *CSD) WriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 } +func (c *CSD) ReadBlockMisalignment() bool { return c.data[6]&(1<<5) != 0 } + +// ImplementsDSR returns whether the card implements the DSR register. +func (c *CSD) ImplementsDSR() bool { return c.data[6]&(1<<4) != 0 } + +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 +} + +func (c *CSD) String() string { + buf := make([]byte, 0, 64) + return string(c.appendf(buf, '\n')) +} + +func (c *CSD) appendf(b []byte, delim byte) []byte { + b = appendnum(b, "CSDStructure", uint64(c.CSDStructure()), 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.CCC()), delim) + b = appendnum(b, "ReadBlockLen", uint64(c.ReadBlockLen()), delim) + b = appendbit(b, "ReadBlockPartial", c.ReadBlockPartial(), delim) + b = appendbit(b, "WriteBlockMisalignment", c.WriteBlockMisalignment(), delim) + b = appendbit(b, "ReadBlockMisalignment", c.ReadBlockMisalignment(), delim) + b = appendbit(b, "ImplementsDSR", c.ImplementsDSR(), 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] +} + +const ( + CMD0_GO_IDLE_STATE = 0 + CMD1_SEND_OP_CND = 1 + CMD2_ALL_SEND_CID = 2 + CMD3_SEND_RELATIVE_ADDR = 3 + CMD4_SET_DSR = 4 + CMD6_SWITCH_FUNC = 6 + CMD7_SELECT_DESELECT_CARD = 7 + CMD8_SEND_IF_COND = 8 + CMD9_SEND_CSD = 9 + CMD10_SEND_CID = 10 + CMD12_STOP_TRANSMISSION = 12 + CMD13_SEND_STATUS = 13 + CMD15_GO_INACTIVE_STATE = 15 + CMD16_SET_BLOCKLEN = 16 + CMD17_READ_SINGLE_BLOCK = 17 + CMD18_READ_MULTIPLE_BLOCK = 18 + CMD24_WRITE_BLOCK = 24 + CMD25_WRITE_MULTIPLE_BLOCK = 25 + CMD27_PROGRAM_CSD = 27 + CMD28_SET_WRITE_PROT = 28 + CMD29_CLR_WRITE_PROT = 29 + CMD30_SEND_WRITE_PROT = 30 + CMD32_ERASE_WR_BLK_START_ADDR = 32 + CMD33_ERASE_WR_BLK_END_ADDR = 33 + CMD38_ERASE = 38 + CMD42_LOCK_UNLOCK = 42 + CMD55_APP_CMD = 55 + CMD56_GEN_CMD = 56 + CMD58_READ_OCR = 58 + CMD59_CRC_ON_OFF = 59 + ACMD6_SET_BUS_WIDTH = 6 + ACMD13_SD_STATUS = 13 + ACMD22_SEND_NUM_WR_BLOCKS = 22 + ACMD23_SET_WR_BLK_ERASE_COUNT = 23 + ACMD41_SD_APP_OP_COND = 41 + ACMD42_SET_CLR_CARD_DETECT = 42 + ACMD51_SEND_SCR = 51 + ACMD18_SECURE_READ_MULTI_BLOCK = 18 + ACMD25_SECURE_WRITE_MULTI_BLOCK = 25 + ACMD26_SECURE_WRITE_MKB = 26 + ACMD38_SECURE_ERASE = 38 + ACMD43_GET_MKB = 43 + ACMD44_GET_MID = 44 + ACMD45_SET_CER_RN1 = 45 + ACMD46_SET_CER_RN2 = 46 + ACMD47_SET_CER_RES2 = 47 + ACMD48_SET_CER_RES1 = 48 + ACMD49_CHANGE_SECURE_AREA = 49 +) + +type ( + TransferSpeed uint8 + TAAC uint8 +) + +var log10table = [...]int64{ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, +} + +// RateMegabits returns the transfer rate in megabits 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 +} + +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 +) + +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 } + +func b2u8(b bool) uint8 { + if b { + return 1 + } + return 0 +} diff --git a/sd/timer.go b/sd/timer.go new file mode 100644 index 000000000..53c275437 --- /dev/null +++ b/sd/timer.go @@ -0,0 +1,22 @@ +package sd + +import ( + "time" +) + +var timeoutTimer [2]timer + +type timer struct { + start int64 + timeout int64 +} + +func setTimeout(timerID int, timeout time.Duration) *timer { + timeoutTimer[timerID].start = time.Now().UnixNano() + timeoutTimer[timerID].timeout = timeout.Nanoseconds() + return &timeoutTimer[timerID] +} + +func (t timer) expired() bool { + return time.Now().UnixNano() > (t.start + t.timeout) +} From 2f85c8bd04cf14bc9479f11f59d7122b9985ac3a Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 12:39:10 -0300 Subject: [PATCH 02/25] CSD logic shared between V1 and V2 --- examples/sd/main.go | 14 ++- sd/card.go | 68 ++++++++-- sd/card_test.go | 16 --- sd/definitions.go | 295 +++++++++++++++++++++++++++++++++++--------- sd/timer.go | 22 ---- 5 files changed, 309 insertions(+), 106 deletions(-) delete mode 100644 sd/timer.go diff --git a/examples/sd/main.go b/examples/sd/main.go index 440180c83..555da6396 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -38,9 +38,21 @@ func main() { if err != nil { panic(err.Error()) } + cid := sdcard.CID() + pname := cid.ProductName() csd := sdcard.CSD() - fmt.Printf("cid=%+v\ncsd=%+v\ncsdfmt=\n%s\ndone!", sdcard.CID(), csd, csd.String()) + valid := csd.IsValid() + if !valid { + data := csd.RawCopy() + crc := sd.CRC7(data[:15]) + always1 := data[15]&(1<<7) != 0 + println("CSD not valid got", crc, "want", data[15]&^(1<<7), "always1:", always1) + } else { + println("CSD valid!") + } + fmt.Printf("name=%s\ncsd=\n%s\n", pname, csd.String()) + return var buf [512]byte for i := 1; i < 11; i += 1 { time.Sleep(time.Millisecond) diff --git a/sd/card.go b/sd/card.go index 190fb83c4..51f32dbaa 100644 --- a/sd/card.go +++ b/sd/card.go @@ -35,6 +35,7 @@ type Card struct { cid CID csd CSD lastCRC uint16 + timers [2]timer } func NewCard(spi drivers.SPI, cs digitalPinout) *Card { @@ -62,7 +63,7 @@ func (d *Card) Init() error { // CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts) ok := false - tm := setTimeout(0, 2*time.Second) + tm := d.timers[0].setTimeout(2 * time.Second) for !tm.expired() { // Wait up to 2 seconds to be the same as the Arduino result, err := d.cmd(CMD0_GO_IDLE_STATE, 0, 0x95) @@ -121,7 +122,7 @@ func (d *Card) Init() error { // check for timeout ok = false - tm = setTimeout(0, 2*time.Second) + tm = tm.setTimeout(2 * time.Second) for !tm.expired() { r1, err = d.appCmd(ACMD41_SD_APP_OP_COND, arg) if err != nil { @@ -325,8 +326,10 @@ func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { buf[0] = 0x40 | cmd binary.BigEndian.PutUint32(buf[1:5], arg) buf[5] = crc - d.bus.Tx(buf, nil) - + err := d.bus.Tx(buf, nil) + if err != nil { + return 0, err + } if cmd == 12 { // skip 1 byte d.bus.Transfer(byte(0xFF)) @@ -352,7 +355,7 @@ func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { } func (d *Card) waitNotBusy(timeout time.Duration) error { - tm := setTimeout(1, timeout) + tm := d.timers[1].setTimeout(timeout) for !tm.expired() { r, err := d.bus.Transfer(byte(0xFF)) if err != nil { @@ -368,8 +371,7 @@ func (d *Card) waitNotBusy(timeout time.Duration) error { func (d *Card) waitStartBlock() error { status := byte(0xFF) - - tm := setTimeout(0, 300*time.Millisecond) + tm := d.timers[0].setTimeout(300 * time.Millisecond) for !tm.expired() { var err error status, err = d.bus.Transfer(byte(0xFF)) @@ -380,6 +382,7 @@ func (d *Card) waitStartBlock() error { if status != 0xFF { break } + runtime.Gosched() } if status != 254 { @@ -396,14 +399,65 @@ type response1Err struct { } 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, } } + +var timeoutTimer [2]timer + +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 +} diff --git a/sd/card_test.go b/sd/card_test.go index 91fa1293e..419723a2e 100644 --- a/sd/card_test.go +++ b/sd/card_test.go @@ -27,19 +27,3 @@ func TestCRC(t *testing.T) { } } } - -func CRC16(buf []byte) (sum uint16) { - const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1 - var crc uint16 = 0x0000 // Initial value - 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 -} diff --git a/sd/definitions.go b/sd/definitions.go index 433d8bfcb..0cb26ec2c 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -48,54 +48,17 @@ func DecodeCID(b []byte) (CID, error) { return cid, nil } -func (c *CID) ProductName() []byte { - return upToNull(c.prodName[:]) +func (c *CID) ProductName() string { + return string(upToNull(c.prodName[:])) } func (c *CID) ProductRevision() (n, m uint8) { return c.productRev >> 4, c.productRev & 0x0F } -/* -CSD Register Fields: -Name: Field: Width: Value: CSD-Slice: -CSD Structure CSD_STRUCTURE 2 00b R[127:128] -TAAC TAAC 8 00h R[119:113] -NSAC NSAC 8 00h R[111:105] -TRAN_SPEED TRAN_SPEED 8 32h or 5Ah R[103:97] -CCC CCC 12 01x110110101b R[95:85] -READ_BL_LEN READ_BL_LEN 4 xh R[83:81] -READ_BL_PARTIAL READ_BL_PARTIAL 1 1b R[79:80] -WRITE_BLK_MISALIGN WRITE_BLK_MISALIGN 1 xb R[78:79] -READ_BLK_MISALIGN READ_BLK_MISALIGN 1 xb R[77:78] -DSR_IMP DSR_IMP 1 xb R[76:77] -C_SIZE C_SIZE 12 xxxh R[73:63] -VDD_R_CURR_MIN VDD_R_CURR_MIN 3 xxxb R[61:60] -VDD_R_CURR_MAX VDD_R_CURR_MAX 3 xxxb R[58:57] -VDD_W_CURR_MIN VDD_W_CURR_MIN 3 xxxb R[55:54] -VDD_W_CURR_MAX VDD_W_CURR_MAX 3 xxxb R[52:51] -C_SIZE_MULT C_SIZE_MULT 3 xxxb R[49:48] -ERASE_BLK_EN ERASE_BLK_EN 1 xb R[46:47] -SECTOR_SIZE SECTOR_SIZE 7 xxxxxxxb R[45:40] -WP_GRP_SIZE WP_GRP_SIZE 7 xxxxxxxb R[38:33] -WP_GRP_ENABLE WP_GRP_ENABLE 1 xb R[31:32] -R2W_FACTOR R2W_FACTOR 2 xxxb R[28:27] -WRITE_BL_LEN WRITE_BL_LEN 4 xxxxb R[25:23] -WRITE_BL_PARTIAL WRITE_BL_PARTIAL 1 xb R[21:22] -FILE_FORMAT_GRP FILE_FORMAT_GRP 1 xb R[15:16] -COPY COPY 1 xb R[14:15] -PERM_WRITE_PROTECT PERM_WRITE_PROTECT 1 xb R[13:14] -TMP_WRITE_PROTECT TMP_WRITE_PROTECT 1 xb R[12:13] -FILE_FORMAT FILE_FORMAT 2 xxb R[11:11] -CRC CRC 7 xxxxxxxb R[7:2] -Not Used - 1 1b R[0:1] - -Note: 'R' indicates read-only fields, 'R/W' indicates read/write fields. -The values in the 'CSD-Slice' column indicate the bit positions in the CSD register. -*/ - // 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. +// 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 } @@ -104,6 +67,10 @@ type CSDv1 struct { CSD } +type CSDv2 struct { + CSD +} + func DecodeCSD(b []byte) (CSD, error) { if len(b) < 16 { return CSD{}, io.ErrShortBuffer @@ -117,57 +84,203 @@ func DecodeCSD(b []byte) (CSD, error) { func (c *CSD) CSDStructure() uint8 { return c.data[0] >> 6 } func (c CSD) MustV1() CSDv1 { - if c.CSDStructure() != 1 { - panic("CSD is not version 1") + 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() uint8 { return c.data[2] } +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]) } -// CCC returns the Card Command Classes. -func (c *CSD) CCC() uint16 { - return uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4 +// 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() uint16 { return 1 << (c.data[5] & 0x0F) } -func (c *CSD) ReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 } -func (c *CSD) WriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 } -func (c *CSD) ReadBlockMisalignment() bool { return c.data[6]&(1<<5) != 0 } +// 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 } -// ImplementsDSR returns whether the card implements the DSR register. +// 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] & 0b111_1111 } + +// 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. + got := CRC7(c.data[:15]) + return got|(1<<7) == c.data[15] +} + +// ImplementsDSR defines if the configurable driver stage is integrated on the card. func (c *CSD) ImplementsDSR() bool { return c.data[6]&(1<<4) != 0 } -func (c *CSDv1) CSize() uint16 { +// EraseSectorSizeInBlocks represents how much memory is erased in an erase +// command in multiple of block size. +func (c *CSD) EraseSectorSizeInBlocks() 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) +} + +// WriteBlockLength represents maximum write data block length in bytes. +func (c *CSD) WriteBlockLength() uint16 { + 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 uint64) { + switch c.CSDStructure() { + case 0: + v1 := c.MustV1() + size = uint64(v1.DeviceCapacity()) + case 1: + v2 := c.MustV2() + size = v2.DeviceCapacity() + } + return size +} + +// After byte 5 CSDv1 and CSDv2 differ in structure at some fields. + +// DeviceCapacity returns the device capacity in bytes. +func (c *CSDv2) DeviceCapacity() uint64 { + csize := c.csize() + return uint64(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) - return string(c.appendf(buf, '\n')) + 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, "CSDStructure", uint64(c.CSDStructure()), delim) + b = appendnum(b, "Version", uint64(c.CSDStructure()+1), delim) + b = appendnum(b, "Capacity(bytes)", 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.CCC()), delim) + b = appendnum(b, "CCC", uint64(c.CommandClasses()), delim) b = appendnum(b, "ReadBlockLen", uint64(c.ReadBlockLen()), delim) - b = appendbit(b, "ReadBlockPartial", c.ReadBlockPartial(), delim) - b = appendbit(b, "WriteBlockMisalignment", c.WriteBlockMisalignment(), delim) - b = appendbit(b, "ReadBlockMisalignment", c.ReadBlockMisalignment(), 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.WriteBlockLength()), 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 } @@ -246,11 +359,38 @@ const ( ACMD49_CHANGE_SECURE_AREA = 49 ) +// CSD enum types. type ( - TransferSpeed uint8 - TAAC uint8 + 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, @@ -298,3 +438,38 @@ func b2u8(b bool) uint8 { } return 0 } + +// CRC16 computes the CRC16 checksum for a given payload using the CRC-16-CCITT polynomial. +func CRC16(buf []byte) (sum uint16) { + const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1 + var crc uint16 = 0x0000 // Initial value + 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) uint8 { + const poly uint8 = 0x09 // Generator polynomial G(x) = x^7 + x^3 + 1 + var crc uint8 = 0x00 // Initial value + + for _, b := range data { + crc ^= b // Initial XOR + for i := 0; i < 8; i++ { // Process each bit + if crc&0x80 != 0 { + crc = (crc << 1) ^ poly + } else { + crc <<= 1 + } + } + } + return crc >> 1 +} diff --git a/sd/timer.go b/sd/timer.go deleted file mode 100644 index 53c275437..000000000 --- a/sd/timer.go +++ /dev/null @@ -1,22 +0,0 @@ -package sd - -import ( - "time" -) - -var timeoutTimer [2]timer - -type timer struct { - start int64 - timeout int64 -} - -func setTimeout(timerID int, timeout time.Duration) *timer { - timeoutTimer[timerID].start = time.Now().UnixNano() - timeoutTimer[timerID].timeout = timeout.Nanoseconds() - return &timeoutTimer[timerID] -} - -func (t timer) expired() bool { - return time.Now().UnixNano() > (t.start + t.timeout) -} From 46cd56951c62b7c3877fa3eb20d5a35d54edd78f Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 14:17:04 -0300 Subject: [PATCH 03/25] failing CRC7 implementation --- examples/sd/main.go | 18 +++--- sd/card.go | 133 ++++++++++++++++++++++++++------------------ sd/card_test.go | 59 +++++++++++++++++++- sd/definitions.go | 64 ++++++++++++++++----- 4 files changed, 198 insertions(+), 76 deletions(-) diff --git a/examples/sd/main.go b/examples/sd/main.go index 555da6396..f4e5f39c6 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -32,7 +32,7 @@ func main() { if err != nil { panic(err.Error()) } - sdcard := sd.NewCard(spibus, SPI_CS_PIN.Set) + sdcard := sd.NewSPICard(spibus, SPI_CS_PIN.Set) err = sdcard.Init() if err != nil { @@ -47,20 +47,24 @@ func main() { data := csd.RawCopy() crc := sd.CRC7(data[:15]) always1 := data[15]&(1<<7) != 0 - println("CSD not valid got", crc, "want", data[15]&^(1<<7), "always1:", always1) + fmt.Printf("ourCRC7=%#b theirCRC7=%#b for data %d\n", crc, csd.CRC7(), data[:15]) + println("CSD not valid got", crc, "want", csd.CRC7(), "always1:", always1) + return } else { println("CSD valid!") } + fmt.Printf("name=%s\ncsd=\n%s\n", pname, csd.String()) - return + var buf [512]byte - for i := 1; i < 11; i += 1 { - time.Sleep(time.Millisecond) - err = sdcard.ReadBlock(uint32(i), buf[:]) + for i := 0; i < 11; i += 1 { + time.Sleep(100 * time.Millisecond) + err = sdcard.ReadBlock(int64(i), buf[:]) if err != nil { println("err reading block", i, ":", err.Error()) continue } - fmt.Printf("block %d crc=%#x:\n\t%#x\n", i, sdcard.LastReadCRC(), buf[:]) + expectCRC := sd.CRC16(buf[:]) + fmt.Printf("block %d theircrc=%#x ourcrc=%#x:\n\t%#x\n", i, sdcard.LastReadCRC(), expectCRC, buf[:]) } } diff --git a/sd/card.go b/sd/card.go index 51f32dbaa..40914bf02 100644 --- a/sd/card.go +++ b/sd/card.go @@ -3,6 +3,7 @@ package sd import ( "encoding/binary" "errors" + "math" "runtime" "strconv" "time" @@ -21,33 +22,36 @@ var ( errNeed512 = errors.New("sd:need 512 bytes for I/O") errWrite = errors.New("sd:write") errWriteTimeout = errors.New("sd:write timeout") + errOOB = errors.New("sd:oob block access") + errNoblocks = errors.New("sd:no readable blocks") ) type digitalPinout func(b bool) -type Card struct { - bus drivers.SPI - cs digitalPinout - bufcmd [6]byte - buf [512]byte - bufTok [1]byte - kind CardKind - cid CID - csd CSD - lastCRC uint16 - timers [2]timer +type SPICard struct { + bus drivers.SPI + cs digitalPinout + bufcmd [6]byte + buf [512]byte + bufTok [1]byte + kind CardKind + cid CID + csd CSD + lastCRC uint16 + timers [2]timer + numblocks int64 } -func NewCard(spi drivers.SPI, cs digitalPinout) *Card { - return &Card{bus: spi, cs: cs} +func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { + return &SPICard{bus: spi, cs: cs} } -func (c *Card) csEnable(b bool) { c.cs(!b) } +func (c *SPICard) csEnable(b bool) { c.cs(!b) } // LastReadCRC returns the CRC for the last ReadBlock operation. -func (c *Card) LastReadCRC() uint16 { return c.lastCRC } +func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC } -func (d *Card) Init() error { +func (d *SPICard) Init() error { dummy := d.buf[:] for i := range dummy { dummy[i] = 0xFF @@ -92,7 +96,7 @@ func (d *Card) Init() error { status := byte(0) for i := 0; i < 3; i++ { var err error - status, err = d.bus.Transfer(byte(0xFF)) + status, err = d.bus.Transfer(0xFF) if err != nil { return err } @@ -103,7 +107,7 @@ func (d *Card) Init() error { for i := 3; i < 4; i++ { var err error - status, err = d.bus.Transfer(byte(0xFF)) + status, err = d.bus.Transfer(0xFF) if err != nil { return err } @@ -143,7 +147,7 @@ func (d *Card) Init() error { return err } - statusb, err := d.bus.Transfer(byte(0xFF)) + statusb, err := d.bus.Transfer(0xFF) if err != nil { return err } @@ -152,7 +156,7 @@ func (d *Card) Init() error { } // discard rest of ocr - contains allowed voltage range for i := 1; i < 4; i++ { - d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(0xFF) } } err = d.cmdEnsure0Status(CMD16_SET_BLOCKLEN, 0x0200, 0xff) @@ -169,20 +173,33 @@ func (d *Card) Init() error { if err != nil { return err } + nb := d.csd.NumberOfBlocks() + if nb > math.MaxUint32 { + return errCardNotSupported + } else if nb == 0 { + return errNoblocks + } + d.numblocks = int64(nb) return nil } -// ReadData reads 512 bytes from sdcard into dst. -func (d *Card) ReadBlock(block uint32, dst []byte) error { +func (d *SPICard) NumberOfBlocks() uint64 { + return uint64(d.numblocks) +} + +// ReadBlock reads 512 bytes from sdcard into dst. +func (d *SPICard) ReadBlock(block int64, dst []byte) error { if len(dst) != 512 { return errNeed512 + } else if block >= d.numblocks { + return errOOB } // use address if not SDHC card if d.kind != TypeSDHC { block <<= 9 } - err := d.cmdEnsure0Status(CMD17_READ_SINGLE_BLOCK, block, 0xFF) + err := d.cmdEnsure0Status(CMD17_READ_SINGLE_BLOCK, uint32(block), 0xFF) if err != nil { return err } @@ -198,23 +215,25 @@ func (d *Card) ReadBlock(block uint32, dst []byte) error { } // skip CRC (2byte) - hi, _ := d.bus.Transfer(byte(0xFF)) - lo, _ := d.bus.Transfer(byte(0xFF)) + hi, _ := d.bus.Transfer(0xFF) + lo, _ := d.bus.Transfer(0xFF) d.lastCRC = uint16(hi)<<8 | uint16(lo) return nil } // WriteBlock writes 512 bytes from dst to sdcard. -func (d *Card) WriteBlock(block uint32, src []byte) error { +func (d *SPICard) WriteBlock(block int64, src []byte) error { if len(src) != 512 { return errNeed512 + } else if block >= d.numblocks { + return errOOB } // use address if not SDHC card if d.kind != TypeSDHC { block <<= 9 } - err := d.cmdEnsure0Status(CMD24_WRITE_BLOCK, block, 0xFF) + err := d.cmdEnsure0Status(CMD24_WRITE_BLOCK, uint32(block), 0xFF) if err != nil { return err } @@ -229,11 +248,11 @@ func (d *Card) WriteBlock(block uint32, src []byte) error { } // send dummy CRC (2 byte) - d.bus.Transfer(byte(0xFF)) - d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(0xFF) + d.bus.Transfer(0xFF) // Data Resp. - r, err := d.bus.Transfer(byte(0xFF)) + r, err := d.bus.Transfer(0xFF) if err != nil { return err } @@ -251,12 +270,12 @@ func (d *Card) WriteBlock(block uint32, src []byte) error { } // CID returns a copy of the Card Identification Register value last read. -func (d *Card) CID() CID { return d.cid } +func (d *SPICard) CID() CID { return d.cid } // CSD returns a copy of the Card Specific Data Register value last read. -func (d *Card) CSD() CSD { return d.csd } +func (d *SPICard) CSD() CSD { return d.csd } -func (d *Card) readCID() (CID, error) { +func (d *SPICard) readCID() (CID, error) { buf := d.buf[len(d.buf)-16:] if err := d.readRegister(CMD10_SEND_CID, buf); err != nil { return CID{}, err @@ -264,7 +283,7 @@ func (d *Card) readCID() (CID, error) { return DecodeCID(buf) } -func (d *Card) readCSD() (CSD, error) { +func (d *SPICard) readCSD() (CSD, error) { buf := d.buf[len(d.buf)-16:] if err := d.readRegister(CMD9_SEND_CSD, buf); err != nil { return CSD{}, err @@ -272,7 +291,7 @@ func (d *Card) readCSD() (CSD, error) { return DecodeCSD(buf) } -func (d *Card) readRegister(cmd uint8, dst []byte) error { +func (d *SPICard) readRegister(cmd uint8, dst []byte) error { err := d.cmdEnsure0Status(cmd, 0, 0xFF) if err != nil { return err @@ -282,20 +301,20 @@ func (d *Card) readRegister(cmd uint8, dst []byte) error { } // transfer data for i := uint16(0); i < 16; i++ { - r, err := d.bus.Transfer(byte(0xFF)) + r, err := d.bus.Transfer(0xFF) if err != nil { return err } dst[i] = r } // skip CRC. - d.bus.Transfer(byte(0xFF)) - d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(0xFF) + d.bus.Transfer(0xFF) d.csEnable(false) return nil } -func (d *Card) appCmd(cmd byte, arg uint32) (response1, error) { +func (d *SPICard) appCmd(cmd byte, arg uint32) (response1, error) { status, err := d.cmd(CMD55_APP_CMD, 0, 0xFF) if err != nil { return status, err @@ -303,7 +322,7 @@ func (d *Card) appCmd(cmd byte, arg uint32) (response1, error) { return d.cmd(cmd, arg, 0xFF) } -func (d *Card) cmdEnsure0Status(cmd byte, arg uint32, crc byte) error { +func (d *SPICard) cmdEnsure0Status(cmd byte, arg uint32, crc byte) error { status, err := d.cmd(cmd, arg, crc) if err != nil { return err @@ -314,7 +333,17 @@ func (d *Card) cmdEnsure0Status(cmd byte, arg uint32, crc byte) error { return nil } -func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { +func putCmd(dst []byte, cmd byte, arg uint32) { + if len(dst) < 6 { + panic("bad buflength") + } + dst[0] = 0x40 | cmd + binary.BigEndian.PutUint32(dst[1:5], arg) + dst[5] = CRC7(dst[:5])<<1 | 1 // CRC and stop bit. +} + +// 0100000000000000000000000000000000000000 +func (d *SPICard) cmd(cmd byte, arg uint32, crc byte) (response1, error) { d.csEnable(true) if cmd != 12 { @@ -322,9 +351,8 @@ func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { } // create and send the command - buf := d.bufcmd[:] - buf[0] = 0x40 | cmd - binary.BigEndian.PutUint32(buf[1:5], arg) + buf := d.bufcmd[:6] + putCmd(buf, cmd, arg) buf[5] = crc err := d.bus.Tx(buf, nil) if err != nil { @@ -332,14 +360,13 @@ func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { } if cmd == 12 { // skip 1 byte - d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(0xFF) } // wait for the response (response[7] == 0) - d.buf[0] = 0xFF - dummy := d.buf[:1] + buf[0] = 0xFF for i := 0; i < 0xFFFF; i++ { - d.bus.Tx(dummy, d.bufTok[:]) + d.bus.Tx(buf[:1], d.bufTok[:]) response := response1(d.bufTok[0]) if (response & 0x80) == 0 { return response, nil @@ -349,15 +376,15 @@ func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { // TODO //// timeout d.csEnable(false) - d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(0xFF) return 0xFF, nil // -1 } -func (d *Card) waitNotBusy(timeout time.Duration) error { +func (d *SPICard) waitNotBusy(timeout time.Duration) error { tm := d.timers[1].setTimeout(timeout) for !tm.expired() { - r, err := d.bus.Transfer(byte(0xFF)) + r, err := d.bus.Transfer(0xFF) if err != nil { return err } @@ -369,12 +396,12 @@ func (d *Card) waitNotBusy(timeout time.Duration) error { return nil } -func (d *Card) waitStartBlock() error { +func (d *SPICard) waitStartBlock() error { status := byte(0xFF) tm := d.timers[0].setTimeout(300 * time.Millisecond) for !tm.expired() { var err error - status, err = d.bus.Transfer(byte(0xFF)) + status, err = d.bus.Transfer(0xFF) if err != nil { d.csEnable(false) return err diff --git a/sd/card_test.go b/sd/card_test.go index 419723a2e..f6dda746b 100644 --- a/sd/card_test.go +++ b/sd/card_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestCRC(t *testing.T) { +func TestCRC16(t *testing.T) { tests := []struct { block string wantcrc uint16 @@ -27,3 +27,60 @@ func TestCRC(t *testing.T) { } } } + +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, 5: 0}, // CMD0, arg=0 + wantCRC: 0b1001010, + }, + { + data: []byte{cmdSendMask | 17, 5: 0}, // CMD17, arg=0 + wantCRC: 0b0101010, + }, + { + data: []byte{17, 4: 0b1001, 5: 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: 0b1100101, + }, + } + + for _, tt := range tests { + gotcrc := CRC7(tt.data[:]) + if gotcrc != tt.wantCRC { + t.Errorf("got crc=%#b, want=%#b", gotcrc, tt.wantCRC) + } + } + + cmdTests := []struct { + cmd byte + arg uint32 + wantCRC uint8 + }{ + { + cmd: CMD0_GO_IDLE_STATE, + arg: 0, + wantCRC: 0x95, + }, + { + cmd: CMD8_SEND_IF_COND, + 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) + } + } +} diff --git a/sd/definitions.go b/sd/definitions.go index 0cb26ec2c..a7a331dff 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -196,6 +196,15 @@ func (c *CSD) DeviceCapacity() (size uint64) { return size } +// NumberOfBlocks returns amount of readable blocks in the device given by Capacity/ReadBlockLength. +func (c *CSD) NumberOfBlocks() (numBlocks uint64) { + rblocks := c.ReadBlockLen() + if rblocks == 0 { + return 0 + } + return c.DeviceCapacity() / uint64(rblocks) +} + // After byte 5 CSDv1 and CSDv2 differ in structure at some fields. // DeviceCapacity returns the device capacity in bytes. @@ -440,9 +449,9 @@ func b2u8(b bool) uint8 { } // CRC16 computes the CRC16 checksum for a given payload using the CRC-16-CCITT polynomial. -func CRC16(buf []byte) (sum uint16) { +func CRC16(buf []byte) (crc uint16) { const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1 - var crc uint16 = 0x0000 // Initial value + for _, b := range buf { crc ^= (uint16(b) << 8) // Shift byte into MSB of crc for i := 0; i < 8; i++ { // Process each bit @@ -457,19 +466,44 @@ func CRC16(buf []byte) (sum uint16) { } // CRC7 computes the CRC7 checksum for a given payload using the polynomial x^7 + x^3 + 1. -func CRC7(data []byte) uint8 { - const poly uint8 = 0x09 // Generator polynomial G(x) = x^7 + x^3 + 1 - var crc uint8 = 0x00 // Initial value - +func CRC7(data []byte) (crc uint8) { for _, b := range data { - crc ^= b // Initial XOR - for i := 0; i < 8; i++ { // Process each bit - if crc&0x80 != 0 { - crc = (crc << 1) ^ poly - } else { - crc <<= 1 - } - } + crc = crc7_table[crc^b] } - return crc >> 1 + 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, } From 3d491553dd5dbaabde8daec092196787652c623a Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 16:10:31 -0300 Subject: [PATCH 04/25] passing tests --- examples/sd/main.go | 5 ++- sd/card.go | 1 + sd/card_test.go | 10 +++--- sd/definitions.go | 88 ++++++++++++++++++++++++++++++--------------- 4 files changed, 69 insertions(+), 35 deletions(-) diff --git a/examples/sd/main.go b/examples/sd/main.go index f4e5f39c6..b4a42c7f9 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -41,7 +41,10 @@ func main() { cid := sdcard.CID() pname := cid.ProductName() csd := sdcard.CSD() - + if !cid.IsValid() { + copy := cid.RawCopy() + println("CID not valid: theirCRC=", cid.CRC7(), "ourCRC=", sd.CRC7(copy[:15])) + } valid := csd.IsValid() if !valid { data := csd.RawCopy() diff --git a/sd/card.go b/sd/card.go index 40914bf02..ba704fd52 100644 --- a/sd/card.go +++ b/sd/card.go @@ -12,6 +12,7 @@ import ( ) 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") errCmd8 = errors.New("sd:cmd8") diff --git a/sd/card_test.go b/sd/card_test.go index f6dda746b..c4656318c 100644 --- a/sd/card_test.go +++ b/sd/card_test.go @@ -35,27 +35,27 @@ func TestCRC7(t *testing.T) { wantCRC uint8 }{ { // See CRC7 Examples from section 4.5 of the SD Card Physical Layer Simplified Specification. - data: []byte{cmdSendMask, 5: 0}, // CMD0, arg=0 + data: []byte{cmdSendMask, 4: 0}, // CMD0, arg=0 wantCRC: 0b1001010, }, { - data: []byte{cmdSendMask | 17, 5: 0}, // CMD17, arg=0 + data: []byte{cmdSendMask | 17, 4: 0}, // CMD17, arg=0 wantCRC: 0b0101010, }, { - data: []byte{17, 4: 0b1001, 5: 0}, // Response of CMD17 + 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: 0b1100101, + wantCRC: 0b1110010, }, } for _, tt := range tests { gotcrc := CRC7(tt.data[:]) if gotcrc != tt.wantCRC { - t.Errorf("got crc=%#b, want=%#b", gotcrc, tt.wantCRC) + t.Errorf("got crc=%#b, want=%#b for %#b", gotcrc, tt.wantCRC, tt.data) } } diff --git a/sd/definitions.go b/sd/definitions.go index a7a331dff..282ee2951 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -8,6 +8,9 @@ import ( "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 const ( @@ -18,44 +21,66 @@ const ( ) type CID struct { - ManufacturerID uint8 // 0:1 - OEMApplicationID uint16 // 1:3 - prodName [5]byte // 3:8 - // productRevision n.m - productRev byte // 8:9 - ProductSerialNumber uint32 // 9:13 - // Manufacturing date bitfield: - // - yearhi=0:4 - // - reserved=4:8 - // - month=8:12 - // - yearlo=12:16 - date [2]byte // 13:15 -} - -func DecodeCID(b []byte) (CID, error) { + data [16]byte +} + +func DecodeCID(b []byte) (cid CID, _ error) { if len(b) < 16 { return CID{}, io.ErrShortBuffer } - cid := CID{ - ManufacturerID: b[0], - OEMApplicationID: binary.BigEndian.Uint16(b[1:3]), - prodName: [5]byte{b[3], b[4], b[5], b[6], b[7]}, - productRev: b[8], - ProductSerialNumber: binary.BigEndian.Uint32(b[9:13]), - date: [2]byte{b[13], b[14]}, + 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.prodName[:])) + 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) { - return c.productRev >> 4, c.productRev & 0x0F + 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. @@ -77,6 +102,9 @@ func DecodeCSD(b []byte) (CSD, error) { } csd := CSD{} copy(csd.data[:], b) + if !csd.IsValid() { + return csd, errBadCSDCID + } return csd, nil } @@ -129,13 +157,15 @@ func (c *CSD) AllowsWriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 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] & 0b111_1111 } +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. - got := CRC7(c.data[:15]) - return got|(1<<7) == c.data[15] + return c.Always1() && CRC7(c.data[:15]) == c.CRC7() } // ImplementsDSR defines if the configurable driver stage is integrated on the card. @@ -470,7 +500,7 @@ func CRC7(data []byte) (crc uint8) { for _, b := range data { crc = crc7_table[crc^b] } - return crc + return crc >> 1 } var crc7_table = [256]byte{ From 222f3686818b8f04805a3c6cec8335816287ad81 Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 17:04:16 -0300 Subject: [PATCH 05/25] rename commands --- sd/card.go | 98 +++++++++++++++++++++-------------------- sd/definitions.go | 109 +++++++++++++++++++++++++--------------------- 2 files changed, 110 insertions(+), 97 deletions(-) diff --git a/sd/card.go b/sd/card.go index ba704fd52..a85460362 100644 --- a/sd/card.go +++ b/sd/card.go @@ -71,7 +71,7 @@ func (d *SPICard) Init() error { tm := d.timers[0].setTimeout(2 * time.Second) for !tm.expired() { // Wait up to 2 seconds to be the same as the Arduino - result, err := d.cmd(CMD0_GO_IDLE_STATE, 0, 0x95) + result, err := d.cmd(cmdGoIdleState, 0, 0x95) if err != nil { return err } @@ -85,39 +85,38 @@ func (d *SPICard) Init() error { } // CMD8: determine card version - r1, err := d.cmd(CMD8_SEND_IF_COND, 0x01AA, 0x87) + r1, err := d.cmd(cmdSendIfCond, 0x01AA, 0x87) if err != nil { return err } if r1.IllegalCmdError() { d.kind = TypeSD1 return errCardNotSupported - } else { - // r7 response - status := byte(0) - for i := 0; i < 3; i++ { - var err error - status, err = d.bus.Transfer(0xFF) - if err != nil { - return err - } - } - if (status & 0x0F) != 0x01 { - return makeResponseError(response1(status)) + } + // r7 response + status := byte(0) + for i := 0; i < 3; i++ { + var err error + status, err = d.bus.Transfer(0xFF) + if err != nil { + return err } + } + if (status & 0x0F) != 0x01 { + return makeResponseError(response1(status)) + } - for i := 3; i < 4; i++ { - var err error - status, err = d.bus.Transfer(0xFF) - if err != nil { - return err - } - } - if status != 0xAA { - return makeResponseError(response1(status)) + for i := 3; i < 4; i++ { + var err error + status, err = d.bus.Transfer(0xFF) + if err != nil { + return err } - d.kind = TypeSD2 } + if status != 0xAA { + return makeResponseError(response1(status)) + } + d.kind = TypeSD2 // initialize card and send host supports SDHC if SD2 arg := uint32(0) @@ -129,7 +128,7 @@ func (d *SPICard) Init() error { ok = false tm = tm.setTimeout(2 * time.Second) for !tm.expired() { - r1, err = d.appCmd(ACMD41_SD_APP_OP_COND, arg) + r1, err = d.appCmd(acmdSD_APP_OP_COND, arg) if err != nil { return err } @@ -143,7 +142,7 @@ func (d *SPICard) Init() error { // if SD2 read OCR register to check for SDHC card if d.kind == TypeSD2 { - err := d.cmdEnsure0Status(CMD58_READ_OCR, 0, 0xFF) + err := d.cmdEnsure0Status(cmdReadOCR, 0, 0xFF) if err != nil { return err } @@ -160,7 +159,7 @@ func (d *SPICard) Init() error { d.bus.Transfer(0xFF) } } - err = d.cmdEnsure0Status(CMD16_SET_BLOCKLEN, 0x0200, 0xff) + err = d.cmdEnsure0Status(cmdSetBlocklen, 0x0200, 0xff) if err != nil { return err } @@ -200,7 +199,8 @@ func (d *SPICard) ReadBlock(block int64, dst []byte) error { if d.kind != TypeSDHC { block <<= 9 } - err := d.cmdEnsure0Status(CMD17_READ_SINGLE_BLOCK, uint32(block), 0xFF) + + err := d.cmdEnsure0Status(cmdReadSingleBlock, uint32(block), 0) if err != nil { return err } @@ -234,7 +234,7 @@ func (d *SPICard) WriteBlock(block int64, src []byte) error { if d.kind != TypeSDHC { block <<= 9 } - err := d.cmdEnsure0Status(CMD24_WRITE_BLOCK, uint32(block), 0xFF) + err := d.cmdEnsure0Status(cmdWriteBlock, uint32(block), 0xFF) if err != nil { return err } @@ -278,7 +278,7 @@ func (d *SPICard) CSD() CSD { return d.csd } func (d *SPICard) readCID() (CID, error) { buf := d.buf[len(d.buf)-16:] - if err := d.readRegister(CMD10_SEND_CID, buf); err != nil { + if err := d.readRegister(cmdSendCID, buf); err != nil { return CID{}, err } return DecodeCID(buf) @@ -286,13 +286,13 @@ func (d *SPICard) readCID() (CID, error) { func (d *SPICard) readCSD() (CSD, error) { buf := d.buf[len(d.buf)-16:] - if err := d.readRegister(CMD9_SEND_CSD, buf); err != nil { + if err := d.readRegister(cmdSendCSD, buf); err != nil { return CSD{}, err } return DecodeCSD(buf) } -func (d *SPICard) readRegister(cmd uint8, dst []byte) error { +func (d *SPICard) readRegister(cmd command, dst []byte) error { err := d.cmdEnsure0Status(cmd, 0, 0xFF) if err != nil { return err @@ -315,15 +315,15 @@ func (d *SPICard) readRegister(cmd uint8, dst []byte) error { return nil } -func (d *SPICard) appCmd(cmd byte, arg uint32) (response1, error) { - status, err := d.cmd(CMD55_APP_CMD, 0, 0xFF) +func (d *SPICard) appCmd(cmd appcommand, arg uint32) (response1, error) { + status, err := d.cmd(cmdAppCmd, 0, 0xFF) if err != nil { return status, err } - return d.cmd(cmd, arg, 0xFF) + return d.cmd(command(cmd), arg, 0xFF) } -func (d *SPICard) cmdEnsure0Status(cmd byte, arg uint32, crc byte) error { +func (d *SPICard) cmdEnsure0Status(cmd command, arg uint32, crc byte) error { status, err := d.cmd(cmd, arg, crc) if err != nil { return err @@ -334,17 +334,10 @@ func (d *SPICard) cmdEnsure0Status(cmd byte, arg uint32, crc byte) error { return nil } -func putCmd(dst []byte, cmd byte, arg uint32) { - if len(dst) < 6 { - panic("bad buflength") +func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1, error) { + if cmd >= 1<<6 { + panic("invalid SD command") } - dst[0] = 0x40 | cmd - binary.BigEndian.PutUint32(dst[1:5], arg) - dst[5] = CRC7(dst[:5])<<1 | 1 // CRC and stop bit. -} - -// 0100000000000000000000000000000000000000 -func (d *SPICard) cmd(cmd byte, arg uint32, crc byte) (response1, error) { d.csEnable(true) if cmd != 12 { @@ -353,8 +346,17 @@ func (d *SPICard) cmd(cmd byte, arg uint32, crc byte) (response1, error) { // create and send the command buf := d.bufcmd[:6] - putCmd(buf, cmd, arg) - buf[5] = crc + // Start bit is always zero; transmitter bit is one since we are Host. + const transmitterBit = 1 << 6 + buf[0] = transmitterBit | byte(cmd) + binary.BigEndian.PutUint32(buf[1:5], arg) + if precalculatedCRC != 0 { + buf[5] = precalculatedCRC + } else { + // CRC and end bit which is always 1. + buf[5] = crc7noshift(buf[:5]) | 1 + } + err := d.bus.Tx(buf, nil) if err != nil { return 0, err diff --git a/sd/definitions.go b/sd/definitions.go index 282ee2951..19e7c3e2a 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -347,55 +347,62 @@ func upToNull(buf []byte) []byte { return buf[:nullIdx] } +type ( + command byte + appcommand byte +) + +// SD commands and application commands. const ( - CMD0_GO_IDLE_STATE = 0 - CMD1_SEND_OP_CND = 1 - CMD2_ALL_SEND_CID = 2 - CMD3_SEND_RELATIVE_ADDR = 3 - CMD4_SET_DSR = 4 - CMD6_SWITCH_FUNC = 6 - CMD7_SELECT_DESELECT_CARD = 7 - CMD8_SEND_IF_COND = 8 - CMD9_SEND_CSD = 9 - CMD10_SEND_CID = 10 - CMD12_STOP_TRANSMISSION = 12 - CMD13_SEND_STATUS = 13 - CMD15_GO_INACTIVE_STATE = 15 - CMD16_SET_BLOCKLEN = 16 - CMD17_READ_SINGLE_BLOCK = 17 - CMD18_READ_MULTIPLE_BLOCK = 18 - CMD24_WRITE_BLOCK = 24 - CMD25_WRITE_MULTIPLE_BLOCK = 25 - CMD27_PROGRAM_CSD = 27 - CMD28_SET_WRITE_PROT = 28 - CMD29_CLR_WRITE_PROT = 29 - CMD30_SEND_WRITE_PROT = 30 - CMD32_ERASE_WR_BLK_START_ADDR = 32 - CMD33_ERASE_WR_BLK_END_ADDR = 33 - CMD38_ERASE = 38 - CMD42_LOCK_UNLOCK = 42 - CMD55_APP_CMD = 55 - CMD56_GEN_CMD = 56 - CMD58_READ_OCR = 58 - CMD59_CRC_ON_OFF = 59 - ACMD6_SET_BUS_WIDTH = 6 - ACMD13_SD_STATUS = 13 - ACMD22_SEND_NUM_WR_BLOCKS = 22 - ACMD23_SET_WR_BLK_ERASE_COUNT = 23 - ACMD41_SD_APP_OP_COND = 41 - ACMD42_SET_CLR_CARD_DETECT = 42 - ACMD51_SEND_SCR = 51 - ACMD18_SECURE_READ_MULTI_BLOCK = 18 - ACMD25_SECURE_WRITE_MULTI_BLOCK = 25 - ACMD26_SECURE_WRITE_MKB = 26 - ACMD38_SECURE_ERASE = 38 - ACMD43_GET_MKB = 43 - ACMD44_GET_MID = 44 - ACMD45_SET_CER_RN1 = 45 - ACMD46_SET_CER_RN2 = 46 - ACMD47_SET_CER_RES2 = 47 - ACMD48_SET_CER_RES1 = 48 - ACMD49_CHANGE_SECURE_AREA = 49 + 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. @@ -497,10 +504,14 @@ func CRC16(buf []byte) (crc uint16) { // 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 >> 1 + return crc } var crc7_table = [256]byte{ From 66da4422dcdcbe3977a0dff4b9dab387950486af Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 17:38:20 -0300 Subject: [PATCH 06/25] implement waitToken --- sd/card.go | 86 ++++++++++++++++++++++------------------------- sd/definitions.go | 8 +++++ 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/sd/card.go b/sd/card.go index a85460362..89bb00dc6 100644 --- a/sd/card.go +++ b/sd/card.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "errors" "math" - "runtime" "strconv" "time" @@ -19,12 +18,14 @@ var ( errCmdOCR = errors.New("sd:cmd_ocr") errCmdBlkLen = errors.New("sd:cmd_blklen") errAcmdAppCond = errors.New("sd:acmd_appOrCond") - errWaitStartBlock = errors.New("sd:wait start block") + errWaitStartBlock = errors.New("sd:did not find start block token") errNeed512 = errors.New("sd:need 512 bytes for I/O") errWrite = errors.New("sd:write") errWriteTimeout = errors.New("sd:write timeout") + errBusyTimeout = errors.New("sd:busy card timeout") errOOB = errors.New("sd:oob block access") errNoblocks = errors.New("sd:no readable blocks") + errCmdGeneric = errors.New("sd:command error") ) type digitalPinout func(b bool) @@ -41,10 +42,11 @@ type SPICard struct { lastCRC uint16 timers [2]timer numblocks int64 + timeout time.Duration } func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { - return &SPICard{bus: spi, cs: cs} + return &SPICard{bus: spi, cs: cs, timeout: 300 * time.Millisecond} } func (c *SPICard) csEnable(b bool) { c.cs(!b) } @@ -200,7 +202,7 @@ func (d *SPICard) ReadBlock(block int64, dst []byte) error { block <<= 9 } - err := d.cmdEnsure0Status(cmdReadSingleBlock, uint32(block), 0) + err := d.cmdEnsure0Status(cmdReadSingleBlock, uint32(block), 0xff) if err != nil { return err } @@ -261,8 +263,7 @@ func (d *SPICard) WriteBlock(block int64, src []byte) error { return errWrite } - // wait no busy - err = d.waitNotBusy(600 * time.Millisecond) + err = d.waitNotBusy(2 * d.timeout) if err != nil { return errWriteTimeout } @@ -335,19 +336,23 @@ func (d *SPICard) cmdEnsure0Status(cmd command, arg uint32, crc byte) error { } func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1, error) { - if cmd >= 1<<6 { + const transmitterBit = 1 << 6 + if cmd >= transmitterBit { panic("invalid SD command") } d.csEnable(true) - if cmd != 12 { - d.waitNotBusy(300 * time.Millisecond) + if cmd != cmdStopTransmission { + err := d.waitNotBusy(d.timeout) + if err != nil { + return 0, err + } } // create and send the command buf := d.bufcmd[:6] // Start bit is always zero; transmitter bit is one since we are Host. - const transmitterBit = 1 << 6 + buf[0] = transmitterBit | byte(cmd) binary.BigEndian.PutUint32(buf[1:5], arg) if precalculatedCRC != 0 { @@ -366,61 +371,50 @@ func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1 d.bus.Transfer(0xFF) } - // wait for the response (response[7] == 0) - buf[0] = 0xFF for i := 0; i < 0xFFFF; i++ { - d.bus.Tx(buf[:1], d.bufTok[:]) - response := response1(d.bufTok[0]) + tok, _ := d.bus.Transfer(0xff) + response := response1(tok) if (response & 0x80) == 0 { return response, nil } } - // TODO - //// timeout d.csEnable(false) d.bus.Transfer(0xFF) - - return 0xFF, nil // -1 + return 0xFF, errCmdGeneric } func (d *SPICard) waitNotBusy(timeout time.Duration) error { - tm := d.timers[1].setTimeout(timeout) - for !tm.expired() { - r, err := d.bus.Transfer(0xFF) - if err != nil { - return err - } - if r == 0xFF { - return nil - } - runtime.Gosched() + if d.waitToken(timeout, 0xff) { + return nil } - return nil + return errBusyTimeout } func (d *SPICard) waitStartBlock() error { - status := byte(0xFF) - tm := d.timers[0].setTimeout(300 * time.Millisecond) - for !tm.expired() { - var err error - status, err = d.bus.Transfer(0xFF) + if d.waitToken(d.timeout, tokSTART_BLOCK) { + return nil + } + d.csEnable(false) + return errWaitStartBlock +} + +// waitToken transmits over SPI waiting to read a given byte token. If argument tok +// is 0xff then waitToken will wait for a token that does NOT match 0xff. +func (d *SPICard) waitToken(timeout time.Duration, tok byte) bool { + tm := d.timers[1].setTimeout(timeout) + for { + received, err := d.bus.Transfer(0xFF) if err != nil { - d.csEnable(false) - return err + return false } - if status != 0xFF { - break + matchTok := received == tok + if matchTok || (!matchTok && tok == 0xff) { + return true + } else if tm.expired() { + return false } - runtime.Gosched() } - - if status != 254 { - d.csEnable(false) - return errWaitStartBlock - } - - return nil } type response1Err struct { diff --git a/sd/definitions.go b/sd/definitions.go index 19e7c3e2a..ccdc2bea0 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -468,6 +468,14 @@ const ( _R1_PARAMETER_ERROR = 1 << 6 ) +// 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 response1 uint8 func (r response1) IsIdle() bool { return r&_R1_IDLE_STATE != 0 } From ace4a8924bf6a408d1bb379b92df816442c98a25 Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 18:59:28 -0300 Subject: [PATCH 07/25] add status string method --- examples/sd/main.go | 2 +- sd/card.go | 15 +++++++ sd/definitions.go | 100 ++++++++++++++++++++++++++++++++++++++++++++ sd/status_string.go | 79 ++++++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 sd/status_string.go diff --git a/examples/sd/main.go b/examples/sd/main.go index b4a42c7f9..a314ac1e2 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -62,7 +62,7 @@ func main() { var buf [512]byte for i := 0; i < 11; i += 1 { time.Sleep(100 * time.Millisecond) - err = sdcard.ReadBlock(int64(i), buf[:]) + err = sdcard.ReadBlock(0, buf[:]) if err != nil { println("err reading block", i, ":", err.Error()) continue diff --git a/sd/card.go b/sd/card.go index 89bb00dc6..494d1ab26 100644 --- a/sd/card.go +++ b/sd/card.go @@ -43,6 +43,8 @@ type SPICard struct { timers [2]timer numblocks int64 timeout time.Duration + // relative card address. + rca uint32 } func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { @@ -182,6 +184,12 @@ func (d *SPICard) Init() error { return errNoblocks } d.numblocks = int64(nb) + + err = d.readRegister(cmdSendRelativeAddr, d.buf[:4]) + if err != nil { + return err + } + d.rca = binary.BigEndian.Uint32(d.buf[:4]) return nil } @@ -271,6 +279,13 @@ func (d *SPICard) WriteBlock(block int64, src []byte) error { return nil } +func (d *SPICard) ReadStatus() (response1, error) { + if err := d.readRegister(cmdSendStatus, d.buf[:4]); err != nil { + return 0, err + } + return response1(binary.BigEndian.Uint32(d.buf[:4])), nil +} + // CID returns a copy of the Card Identification Register value last read. func (d *SPICard) CID() CID { return d.cid } diff --git a/sd/definitions.go b/sd/definitions.go index ccdc2bea0..4ff33813b 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -476,6 +476,106 @@ const ( tokWRITE_MULT = 0xfc ) +type status uint32 + +// First status bits. +// +//go:generate stringer -type=status -trimprefix=status -output=status_string.go +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() +} + type response1 uint8 func (r response1) IsIdle() bool { return r&_R1_IDLE_STATE != 0 } diff --git a/sd/status_string.go b/sd/status_string.go new file mode 100644 index 000000000..82b77de9f --- /dev/null +++ b/sd/status_string.go @@ -0,0 +1,79 @@ +package sd + +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, '[') + if s == 0 { + return append(b, ']') + } + for bit := 0; bit < 32; bit++ { + if s&(1< Date: Sun, 14 Jan 2024 20:08:57 -0300 Subject: [PATCH 08/25] no crc errors in status; closing the gap? --- examples/sd/main.go | 3 +- sd/card.go | 80 +++-------- sd/definitions.go | 130 ----------------- sd/responses.go | 331 ++++++++++++++++++++++++++++++++++++++++++++ sd/status_string.go | 79 ----------- 5 files changed, 348 insertions(+), 275 deletions(-) create mode 100644 sd/responses.go delete mode 100644 sd/status_string.go diff --git a/examples/sd/main.go b/examples/sd/main.go index a314ac1e2..55a01d155 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -36,7 +36,7 @@ func main() { err = sdcard.Init() if err != nil { - panic(err.Error()) + panic("sd card init:" + err.Error()) } cid := sdcard.CID() pname := cid.ProductName() @@ -61,7 +61,6 @@ func main() { var buf [512]byte for i := 0; i < 11; i += 1 { - time.Sleep(100 * time.Millisecond) err = sdcard.ReadBlock(0, buf[:]) if err != nil { println("err reading block", i, ":", err.Error()) diff --git a/sd/card.go b/sd/card.go index 494d1ab26..b0ff98f60 100644 --- a/sd/card.go +++ b/sd/card.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "errors" "math" - "strconv" + "runtime" "time" "tinygo.org/x/drivers" @@ -44,7 +44,8 @@ type SPICard struct { numblocks int64 timeout time.Duration // relative card address. - rca uint32 + rca uint32 + lastr1 r1 } func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { @@ -55,6 +56,7 @@ func (c *SPICard) csEnable(b bool) { c.cs(!b) } // LastReadCRC returns the CRC for the last ReadBlock operation. func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC } +func (c *SPICard) LastR1() r1 { return c.lastr1 } func (d *SPICard) Init() error { dummy := d.buf[:] @@ -184,7 +186,7 @@ func (d *SPICard) Init() error { return errNoblocks } d.numblocks = int64(nb) - + return nil err = d.readRegister(cmdSendRelativeAddr, d.buf[:4]) if err != nil { return err @@ -386,12 +388,16 @@ func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1 d.bus.Transfer(0xFF) } - for i := 0; i < 0xFFFF; i++ { + tm := d.timers[0].setTimeout(d.timeout) + for { tok, _ := d.bus.Transfer(0xff) response := response1(tok) if (response & 0x80) == 0 { return response, nil + } else if tm.expired() { + break } + runtime.Gosched() } d.csEnable(false) @@ -400,14 +406,14 @@ func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1 } func (d *SPICard) waitNotBusy(timeout time.Duration) error { - if d.waitToken(timeout, 0xff) { + if _, ok := d.waitToken(timeout, 0xff); ok { return nil } return errBusyTimeout } func (d *SPICard) waitStartBlock() error { - if d.waitToken(d.timeout, tokSTART_BLOCK) { + if _, ok := d.waitToken(d.timeout, tokSTART_BLOCK); ok { return nil } d.csEnable(false) @@ -416,76 +422,22 @@ func (d *SPICard) waitStartBlock() error { // waitToken transmits over SPI waiting to read a given byte token. If argument tok // is 0xff then waitToken will wait for a token that does NOT match 0xff. -func (d *SPICard) waitToken(timeout time.Duration, tok byte) bool { +func (d *SPICard) waitToken(timeout time.Duration, tok byte) (byte, bool) { tm := d.timers[1].setTimeout(timeout) for { received, err := d.bus.Transfer(0xFF) if err != nil { - return false + return received, false } matchTok := received == tok if matchTok || (!matchTok && tok == 0xff) { - return true + return received, true } else if tm.expired() { - return false + return received, false } } } -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, - } -} - var timeoutTimer [2]timer type timer struct { diff --git a/sd/definitions.go b/sd/definitions.go index 4ff33813b..4ae58f7c2 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -456,136 +456,6 @@ func (t TAAC) AccessTime() (d time.Duration) { return time.Duration(log10table[t&0b111]) * time.Nanosecond } -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 -) - -// 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 status uint32 - -// First status bits. -// -//go:generate stringer -type=status -trimprefix=status -output=status_string.go -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() -} - -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 } - func b2u8(b bool) uint8 { if b { return 1 diff --git a/sd/responses.go b/sd/responses.go new file mode 100644 index 000000000..b1f4bc82d --- /dev/null +++ b/sd/responses.go @@ -0,0 +1,331 @@ +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 +) + +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, + } +} + +// is part of specification but not used in every implementation out there... +func (d *SPICard) readR1() (resp r1, err error) { + first, ok := d.waitToken(d.timeout, 0xff) + if !ok { + return resp, errBusyTimeout + } + err = d.bus.Tx(nil, d.buf[1:6]) + if err != nil { + return resp, err + } + d.buf[0] = first + copy(resp.data[:], d.buf[:6]) + return resp, nil +} + +// 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/status_string.go b/sd/status_string.go deleted file mode 100644 index 82b77de9f..000000000 --- a/sd/status_string.go +++ /dev/null @@ -1,79 +0,0 @@ -package sd - -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, '[') - if s == 0 { - return append(b, ']') - } - for bit := 0; bit < 32; bit++ { - if s&(1< Date: Sun, 14 Jan 2024 20:16:45 -0300 Subject: [PATCH 09/25] add config baud increase docs --- examples/sd/main.go | 22 ++++++++++++++-------- sd/card.go | 2 ++ sd/definitions.go | 2 +- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/examples/sd/main.go b/examples/sd/main.go index 55a01d155..9df0cc285 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -17,18 +17,19 @@ const ( var ( spibus = machine.SPI0 -) - -func main() { - time.Sleep(time.Second) - SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) - err := spibus.Configure(machine.SPIConfig{ + 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()) } @@ -38,9 +39,14 @@ func main() { 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() pname := cid.ProductName() - csd := sdcard.CSD() if !cid.IsValid() { copy := cid.RawCopy() println("CID not valid: theirCRC=", cid.CRC7(), "ourCRC=", sd.CRC7(copy[:15])) diff --git a/sd/card.go b/sd/card.go index b0ff98f60..13bfbc9e8 100644 --- a/sd/card.go +++ b/sd/card.go @@ -58,6 +58,8 @@ func (c *SPICard) csEnable(b bool) { c.cs(!b) } func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC } func (c *SPICard) LastR1() r1 { return c.lastr1 } +// 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 { dummy := d.buf[:] for i := range dummy { diff --git a/sd/definitions.go b/sd/definitions.go index 4ae58f7c2..2adbb3e35 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -447,7 +447,7 @@ var log10table = [...]int64{ 1000000, } -// RateMegabits returns the transfer rate in megabits per second. +// RateMegabits returns the transfer rate in kilobits per second. func (t TransferSpeed) RateKilobits() int64 { return 100 * log10table[t&0b111] } From 845bd6fe9384f3632e336fab7c2ab7843d9f5f89 Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 14 Jan 2024 22:50:42 -0300 Subject: [PATCH 10/25] remove some of API --- sd/card.go | 36 +++++++++++++++++++++++++++--------- sd/card_test.go | 15 ++++++++++++--- sd/definitions.go | 22 ++++++++++++++-------- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/sd/card.go b/sd/card.go index 13bfbc9e8..59e184811 100644 --- a/sd/card.go +++ b/sd/card.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "errors" "math" - "runtime" "time" "tinygo.org/x/drivers" @@ -43,13 +42,29 @@ type SPICard struct { timers [2]timer numblocks int64 timeout time.Duration + wait time.Duration // relative card address. rca uint32 lastr1 r1 } func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { - return &SPICard{bus: spi, cs: cs, timeout: 300 * time.Millisecond} + 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 } func (c *SPICard) csEnable(b bool) { c.cs(!b) } @@ -61,7 +76,7 @@ func (c *SPICard) LastR1() r1 { return c.lastr1 } // 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 { - dummy := d.buf[:] + dummy := d.buf[:512] for i := range dummy { dummy[i] = 0xFF } @@ -343,8 +358,8 @@ func (d *SPICard) appCmd(cmd appcommand, arg uint32) (response1, error) { return d.cmd(command(cmd), arg, 0xFF) } -func (d *SPICard) cmdEnsure0Status(cmd command, arg uint32, crc byte) error { - status, err := d.cmd(cmd, arg, crc) +func (d *SPICard) cmdEnsure0Status(cmd command, arg uint32, precalcCRC byte) error { + status, err := d.cmd(cmd, arg, precalcCRC) if err != nil { return err } @@ -354,7 +369,7 @@ func (d *SPICard) cmdEnsure0Status(cmd command, arg uint32, crc byte) error { return nil } -func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1, error) { +func (d *SPICard) cmd(cmd command, arg uint32, precalcCRC byte) (response1, error) { const transmitterBit = 1 << 6 if cmd >= transmitterBit { panic("invalid SD command") @@ -374,8 +389,8 @@ func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1 buf[0] = transmitterBit | byte(cmd) binary.BigEndian.PutUint32(buf[1:5], arg) - if precalculatedCRC != 0 { - buf[5] = precalculatedCRC + if precalcCRC != 0 { + buf[5] = precalcCRC } else { // CRC and end bit which is always 1. buf[5] = crc7noshift(buf[:5]) | 1 @@ -399,7 +414,7 @@ func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1 } else if tm.expired() { break } - runtime.Gosched() + d.yield() } d.csEnable(false) @@ -407,6 +422,8 @@ func (d *SPICard) cmd(cmd command, arg uint32, precalculatedCRC byte) (response1 return 0xFF, errCmdGeneric } +func (d *SPICard) yield() { time.Sleep(d.wait) } + func (d *SPICard) waitNotBusy(timeout time.Duration) error { if _, ok := d.waitToken(timeout, 0xff); ok { return nil @@ -437,6 +454,7 @@ func (d *SPICard) waitToken(timeout time.Duration, tok byte) (byte, bool) { } else if tm.expired() { return received, false } + d.yield() } } diff --git a/sd/card_test.go b/sd/card_test.go index c4656318c..2192c65e1 100644 --- a/sd/card_test.go +++ b/sd/card_test.go @@ -60,17 +60,17 @@ func TestCRC7(t *testing.T) { } cmdTests := []struct { - cmd byte + cmd command arg uint32 wantCRC uint8 }{ { - cmd: CMD0_GO_IDLE_STATE, + cmd: cmdGoIdleState, arg: 0, wantCRC: 0x95, }, { - cmd: CMD8_SEND_IF_COND, + cmd: cmdSendIfCond, arg: 0x1AA, wantCRC: 0x87, }, @@ -84,3 +84,12 @@ func TestCRC7(t *testing.T) { } } } + +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 index 2adbb3e35..66976de65 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -88,14 +88,17 @@ 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 @@ -108,18 +111,22 @@ func DecodeCSD(b []byte) (CSD, error) { return csd, nil } -// CSDStructure returns the version of the CSD structure. -func (c *CSD) CSDStructure() uint8 { return c.data[0] >> 6 } +// 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 { + if c.csdStructure() != 0 { panic("CSD is not version 1.0") } return CSDv1{CSD: c} } func (c CSD) MustV2() CSDv2 { - if c.CSDStructure() != 1 { + if c.csdStructure() != 1 { panic("CSD is not version 2.0") } return CSDv2{CSD: c} @@ -215,7 +222,7 @@ 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 uint64) { - switch c.CSDStructure() { + switch c.csdStructure() { case 0: v1 := c.MustV1() size = uint64(v1.DeviceCapacity()) @@ -283,7 +290,7 @@ func (c *CSDv1) VddWriteCurrent() (min, max uint8) { } func (c *CSD) String() string { - version := c.CSDStructure() + 1 + version := c.csdStructure() + 1 if version > 2 { return "" } @@ -298,7 +305,7 @@ 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.CSDStructure()+1), delim) + b = appendnum(b, "Version", uint64(c.Version()), delim) b = appendnum(b, "Capacity(bytes)", c.DeviceCapacity(), delim) b = appendnum(b, "TimeAccess_ns", uint64(c.TAAC().AccessTime()), delim) b = appendnum(b, "NSAC", uint64(c.NSAC()), delim) @@ -466,7 +473,6 @@ func b2u8(b bool) uint8 { // 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 From 7db9e9d6dbc2a284051cca4eddd17fc9b71b1d92 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 01:04:39 -0300 Subject: [PATCH 11/25] still working on consolidation of init --- examples/sd/main.go | 23 +-- sd/card.go | 194 +++++++---------------- sd/definitions.go | 7 +- sd/responses.go | 5 + sd/rustref.go | 368 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 441 insertions(+), 156 deletions(-) create mode 100644 sd/rustref.go diff --git a/examples/sd/main.go b/examples/sd/main.go index 9df0cc285..228fbbb71 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -34,7 +34,7 @@ func main() { 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()) @@ -46,28 +46,11 @@ func main() { err = spibus.Configure(spicfg) cid := sdcard.CID() - pname := cid.ProductName() - if !cid.IsValid() { - copy := cid.RawCopy() - println("CID not valid: theirCRC=", cid.CRC7(), "ourCRC=", sd.CRC7(copy[:15])) - } - valid := csd.IsValid() - if !valid { - data := csd.RawCopy() - crc := sd.CRC7(data[:15]) - always1 := data[15]&(1<<7) != 0 - fmt.Printf("ourCRC7=%#b theirCRC7=%#b for data %d\n", crc, csd.CRC7(), data[:15]) - println("CSD not valid got", crc, "want", csd.CRC7(), "always1:", always1) - return - } else { - println("CSD valid!") - } - - fmt.Printf("name=%s\ncsd=\n%s\n", pname, csd.String()) + fmt.Printf("name=%s\ncsd=\n%s\n", cid.ProductName(), csd.String()) var buf [512]byte for i := 0; i < 11; i += 1 { - err = sdcard.ReadBlock(0, buf[:]) + err = sdcard.ReadBlocks(buf[:], 0) if err != nil { println("err reading block", i, ":", err.Error()) continue diff --git a/sd/card.go b/sd/card.go index 59e184811..d3fd23ed1 100644 --- a/sd/card.go +++ b/sd/card.go @@ -9,40 +9,45 @@ import ( "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") - errCmd8 = errors.New("sd:cmd8") - errCmdOCR = errors.New("sd:cmd_ocr") - errCmdBlkLen = errors.New("sd:cmd_blklen") - errAcmdAppCond = errors.New("sd:acmd_appOrCond") - errWaitStartBlock = errors.New("sd:did not find start block token") - errNeed512 = errors.New("sd:need 512 bytes for I/O") - errWrite = errors.New("sd:write") - errWriteTimeout = errors.New("sd:write timeout") - errBusyTimeout = errors.New("sd:busy card timeout") - errOOB = errors.New("sd:oob block access") - errNoblocks = errors.New("sd:no readable blocks") - errCmdGeneric = errors.New("sd:command error") + errBadCSDCID = errors.New("sd:bad CSD/CID in CRC or always1") + errNoSDCard = errors.New("sd:no card") + errCardNotSupported = errors.New("sd:card not supported") + errCmd8 = errors.New("sd:cmd8") + errCmdOCR = errors.New("sd:cmd_ocr") + errCmdBlkLen = errors.New("sd:cmd_blklen") + errAcmdAppCond = errors.New("sd:acmd_appOrCond") + 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") + errCmdGeneric = errors.New("sd:command error") ) type digitalPinout func(b bool) type SPICard struct { - bus drivers.SPI - cs digitalPinout - bufcmd [6]byte - buf [512]byte - bufTok [1]byte - kind CardKind - cid CID - csd CSD - lastCRC uint16 - timers [2]timer - numblocks int64 - timeout time.Duration - wait time.Duration + bus drivers.SPI + cs digitalPinout + bufcmd [6]byte + buf [512]byte + bufTok [1]byte + kind CardKind + cid CID + csd CSD + lastCRC uint16 + // shift to calculate blocksize, taken from CSD. + blockshift uint8 + timers [2]timer + numblocks int64 + timeout time.Duration + wait time.Duration // relative card address. rca uint32 lastr1 r1 @@ -67,11 +72,12 @@ func (c *SPICard) setTimeout(timeout time.Duration) { c.wait = timeout / 512 } -func (c *SPICard) csEnable(b bool) { c.cs(!b) } +func (c *SPICard) csEnable(b bool) { + c.cs(!b) +} // LastReadCRC returns the CRC for the last ReadBlock operation. func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC } -func (c *SPICard) LastR1() r1 { return c.lastr1 } // 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. @@ -90,6 +96,7 @@ func (d *SPICard) Init() error { d.bus.Tx(dummy[:], nil) // CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts) + println("first timer") ok := false tm := d.timers[0].setTimeout(2 * time.Second) for !tm.expired() { @@ -148,9 +155,11 @@ func (d *SPICard) Init() error { } // check for timeout + println("app cmd") ok = false tm = tm.setTimeout(2 * time.Second) for !tm.expired() { + println("timer") r1, err = d.appCmd(acmdSD_APP_OP_COND, arg) if err != nil { return err @@ -162,7 +171,7 @@ func (d *SPICard) Init() error { if r1 != 0 { return makeResponseError(r1) } - + println("preensure") // if SD2 read OCR register to check for SDHC card if d.kind == TypeSD2 { err := d.cmdEnsure0Status(cmdReadOCR, 0, 0xFF) @@ -182,11 +191,16 @@ func (d *SPICard) Init() error { d.bus.Transfer(0xFF) } } + println("ensure") err = d.cmdEnsure0Status(cmdSetBlocklen, 0x0200, 0xff) if err != nil { return err } + println("get to update csdid") + return d.updateCSDCID() +} +func (d *SPICard) updateCSDCID() (err error) { // read CID d.cid, err = d.readCID() if err != nil { @@ -196,113 +210,23 @@ func (d *SPICard) Init() error { if err != nil { return err } - nb := d.csd.NumberOfBlocks() + blockshift := d.csd.ReadBlockLenShift() + blocklen := uint16(1) << blockshift + capacity := d.csd.DeviceCapacity() + if blocklen == 0 || capacity < uint64(blocklen) { + return errNoblocks + } + nb := capacity / uint64(blocklen) if nb > math.MaxUint32 { return errCardNotSupported - } else if nb == 0 { - return errNoblocks } + d.blockshift = blockshift d.numblocks = int64(nb) return nil - err = d.readRegister(cmdSendRelativeAddr, d.buf[:4]) - if err != nil { - return err - } - d.rca = binary.BigEndian.Uint32(d.buf[:4]) - return nil -} - -func (d *SPICard) NumberOfBlocks() uint64 { - return uint64(d.numblocks) -} - -// ReadBlock reads 512 bytes from sdcard into dst. -func (d *SPICard) ReadBlock(block int64, dst []byte) error { - if len(dst) != 512 { - return errNeed512 - } else if block >= d.numblocks { - return errOOB - } - - // use address if not SDHC card - if d.kind != TypeSDHC { - block <<= 9 - } - - err := d.cmdEnsure0Status(cmdReadSingleBlock, uint32(block), 0xff) - if err != nil { - return err - } - defer d.csEnable(false) - - if err := d.waitStartBlock(); err != nil { - return err - } - buf := d.buf[:] - err = d.bus.Tx(buf, dst) - if err != nil { - return err - } - - // skip CRC (2byte) - hi, _ := d.bus.Transfer(0xFF) - lo, _ := d.bus.Transfer(0xFF) - d.lastCRC = uint16(hi)<<8 | uint16(lo) - return nil } -// WriteBlock writes 512 bytes from dst to sdcard. -func (d *SPICard) WriteBlock(block int64, src []byte) error { - if len(src) != 512 { - return errNeed512 - } else if block >= d.numblocks { - return errOOB - } - - // use address if not SDHC card - if d.kind != TypeSDHC { - block <<= 9 - } - err := d.cmdEnsure0Status(cmdWriteBlock, uint32(block), 0xFF) - if err != nil { - return err - } - defer d.csEnable(false) - // wait 1 byte? - token := byte(0xFE) - d.bus.Transfer(token) - - err = d.bus.Tx(src[:512], nil) - if err != nil { - return err - } - - // send dummy CRC (2 byte) - d.bus.Transfer(0xFF) - d.bus.Transfer(0xFF) - - // Data Resp. - r, err := d.bus.Transfer(0xFF) - if err != nil { - return err - } - if (r & 0x1F) != 0x05 { - return errWrite - } - - err = d.waitNotBusy(2 * d.timeout) - if err != nil { - return errWriteTimeout - } - - return nil -} - -func (d *SPICard) ReadStatus() (response1, error) { - if err := d.readRegister(cmdSendStatus, d.buf[:4]); err != nil { - return 0, err - } - return response1(binary.BigEndian.Uint32(d.buf[:4])), nil +func (d *SPICard) NumberOfBlocks() int64 { + return d.numblocks } // CID returns a copy of the Card Identification Register value last read. @@ -400,23 +324,24 @@ func (d *SPICard) cmd(cmd command, arg uint32, precalcCRC byte) (response1, erro if err != nil { return 0, err } - if cmd == 12 { + if cmd == cmdStopTransmission { // skip 1 byte d.bus.Transfer(0xFF) } - tm := d.timers[0].setTimeout(d.timeout) + tm := d.timers[1].setTimeout(d.timeout) for { tok, _ := d.bus.Transfer(0xff) response := response1(tok) if (response & 0x80) == 0 { + // NOMINAL FUNCTION EXIT HERE return response, nil } else if tm.expired() { break } d.yield() } - + println("============== BAD EXIT ================") d.csEnable(false) d.bus.Transfer(0xFF) return 0xFF, errCmdGeneric @@ -435,7 +360,6 @@ func (d *SPICard) waitStartBlock() error { if _, ok := d.waitToken(d.timeout, tokSTART_BLOCK); ok { return nil } - d.csEnable(false) return errWaitStartBlock } diff --git a/sd/definitions.go b/sd/definitions.go index 66976de65..0c4e3195f 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -13,6 +13,10 @@ import ( 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 @@ -150,7 +154,8 @@ func (c *CSD) CommandClasses() CommandClasses { } // ReadBlockLen returns the Max Read Data Block Length in bytes. -func (c *CSD) ReadBlockLen() uint16 { return 1 << (c.data[5] & 0x0F) } +func (c *CSD) ReadBlockLen() uint16 { 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 } diff --git a/sd/responses.go b/sd/responses.go index b1f4bc82d..7c50763e1 100644 --- a/sd/responses.go +++ b/sd/responses.go @@ -15,6 +15,9 @@ const ( _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 @@ -81,6 +84,8 @@ func makeResponseError(status response1) error { } } +// func (c *SPICard) lastR1() r1 { return c.lastr1 } + // is part of specification but not used in every implementation out there... func (d *SPICard) readR1() (resp r1, err error) { first, ok := d.waitToken(d.timeout, 0xff) diff --git a/sd/rustref.go b/sd/rustref.go new file mode 100644 index 000000000..96b129123 --- /dev/null +++ b/sd/rustref.go @@ -0,0 +1,368 @@ +package sd + +import ( + "encoding/binary" + "errors" + "io" + "math" + "math/bits" + "time" +) + +// 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) + + 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) + 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 = false + if enableCRC { + stat, err := d.card_command(cmdCRCOnOff, 1) + if err != nil { + return err + } else if stat != _R1_IDLE_STATE { + return errors.New("sd:cant enable CRC") + } + } + + tm.setTimeout(d.timeout) + for { + stat, err := d.card_command(cmdSendIfCond, 0x1aa) + if err != nil { + return err + } else if stat == _R1_IDLE_STATE || stat == _R1_ILLEGAL_COMMAND { + 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 + } + for { + 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 +} + +// 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) error { + numblocks, err := d.checkBounds(startBlockIdx, len(dst)) + if err != nil { + return 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 { + _, err = d.card_command(cmdReadSingleBlock, uint32(startBlockIdx)) + if err != nil { + return err + } + return d.read_data(dst) + + } else if numblocks > 1 { + blocksize := 1 << d.blockshift + _, err = d.card_command(cmdReadMultipleBlock, uint32(startBlockIdx)) + if err != nil { + return err + } + + for i := 0; i < numblocks; i++ { + offset := i * blocksize + err = d.read_data(dst[offset : offset+blocksize]) + if err != nil { + return err + } + } + _, err = d.card_command(cmdStopTransmission, 0) + return err + } + panic("unreachable numblocks<=0") +} + +// 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) error { + numblocks, err := d.checkBounds(startBlockIdx, len(data)) + if err != nil { + return 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 { + _, err = d.card_command(cmdWriteBlock, uint32(startBlockIdx)) + if err != nil { + return err + } + err = d.write_data(tokSTART_BLOCK, data) + if err != nil { + return err + } + err = d.wait_not_busy() + if err != nil { + return err + } + status, err := d.card_command(cmdSendStatus, 0) + if err != nil { + return err + } else if status != 0 { + return makeResponseError(response1(status)) + } + status, err = d.receive() + if err != nil { + return err + } else if status != 0 { + return errWrite + } + return nil + + } else if numblocks > 1 { + // Start multi block write. + blocksize := 1 << d.blockshift + _, err = d.card_command(cmdWriteMultipleBlock, uint32(startBlockIdx)) + if err != nil { + return err + } + + for i := 0; i < numblocks; i++ { + offset := i * blocksize + err = d.waitNotBusy(d.timeout) + if err != nil { + return err + } + err = d.write_data(tokWRITE_MULT, data[offset:offset+blocksize]) + if err != nil { + return err + } + } + // Stop the multi write operation. + err = d.waitNotBusy(d.timeout) + if err != nil { + return err + } + return d.send(tokSTOP_TRAN) + } + panic("unreachable numblocks<=0") +} + +func (d *SPICard) checkBounds(startBlockIdx int64, datalen int) (numblocks int, err error) { + if startBlockIdx >= d.numblocks { + return 0, errOOB + } else if startBlockIdx > math.MaxUint32 { + return 0, errCardNotSupported + } + tz := bits.TrailingZeros(uint(datalen)) + if tz < int(d.blockshift) { + return 0, errNeedBlockLenMultiple + } + numblocks = datalen >> d.blockshift + if numblocks == 0 { + return 0, io.ErrShortBuffer + } + return numblocks, nil +} + +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() + 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 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 + for { + status, err = d.receive() + if err != nil { + return err + } + if status != 0xff { + break + } + 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() error { + tm := s.timers[0].setTimeout(s.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 +} From 1b726ef2bd5fd756005766953437177f91c73ebb Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 01:31:03 -0300 Subject: [PATCH 12/25] fully comply --- sd/card.go | 256 +------------------------------------------------- sd/rustref.go | 80 ++++++++++++++-- 2 files changed, 71 insertions(+), 265 deletions(-) diff --git a/sd/card.go b/sd/card.go index d3fd23ed1..d64d281b6 100644 --- a/sd/card.go +++ b/sd/card.go @@ -1,9 +1,7 @@ package sd import ( - "encoding/binary" "errors" - "math" "time" "tinygo.org/x/drivers" @@ -82,147 +80,7 @@ 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 { - dummy := d.buf[:512] - for i := range dummy { - dummy[i] = 0xFF - } - defer d.csEnable(false) - - d.csEnable(true) - // clock card at least 100 cycles with cs high - d.bus.Tx(dummy[:10], nil) - d.csEnable(false) - - d.bus.Tx(dummy[:], nil) - - // CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts) - println("first timer") - ok := false - tm := d.timers[0].setTimeout(2 * time.Second) - for !tm.expired() { - // Wait up to 2 seconds to be the same as the Arduino - result, err := d.cmd(cmdGoIdleState, 0, 0x95) - if err != nil { - return err - } - if result == _R1_IDLE_STATE { - ok = true - break - } - } - if !ok { - return errNoSDCard - } - - // CMD8: determine card version - r1, err := d.cmd(cmdSendIfCond, 0x01AA, 0x87) - if err != nil { - return err - } - if r1.IllegalCmdError() { - d.kind = TypeSD1 - return errCardNotSupported - } - // r7 response - status := byte(0) - for i := 0; i < 3; i++ { - var err error - status, err = d.bus.Transfer(0xFF) - if err != nil { - return err - } - } - if (status & 0x0F) != 0x01 { - return makeResponseError(response1(status)) - } - - for i := 3; i < 4; i++ { - var err error - status, err = d.bus.Transfer(0xFF) - if err != nil { - return err - } - } - if status != 0xAA { - return makeResponseError(response1(status)) - } - d.kind = TypeSD2 - - // initialize card and send host supports SDHC if SD2 - arg := uint32(0) - if d.kind == TypeSD2 { - arg = 0x40000000 - } - - // check for timeout - println("app cmd") - ok = false - tm = tm.setTimeout(2 * time.Second) - for !tm.expired() { - println("timer") - r1, err = d.appCmd(acmdSD_APP_OP_COND, arg) - if err != nil { - return err - } - if r1 == 0 { - break - } - } - if r1 != 0 { - return makeResponseError(r1) - } - println("preensure") - // if SD2 read OCR register to check for SDHC card - if d.kind == TypeSD2 { - err := d.cmdEnsure0Status(cmdReadOCR, 0, 0xFF) - if err != nil { - return err - } - - statusb, err := d.bus.Transfer(0xFF) - if err != nil { - return err - } - if (statusb & 0xC0) == 0xC0 { - d.kind = TypeSDHC - } - // discard rest of ocr - contains allowed voltage range - for i := 1; i < 4; i++ { - d.bus.Transfer(0xFF) - } - } - println("ensure") - err = d.cmdEnsure0Status(cmdSetBlocklen, 0x0200, 0xff) - if err != nil { - return err - } - println("get to update csdid") - return d.updateCSDCID() -} - -func (d *SPICard) updateCSDCID() (err error) { - // read CID - d.cid, err = d.readCID() - if err != nil { - return err - } - d.csd, err = d.readCSD() - if err != nil { - return err - } - blockshift := d.csd.ReadBlockLenShift() - blocklen := uint16(1) << blockshift - capacity := d.csd.DeviceCapacity() - if blocklen == 0 || capacity < uint64(blocklen) { - return errNoblocks - } - nb := capacity / uint64(blocklen) - if nb > math.MaxUint32 { - return errCardNotSupported - } - d.blockshift = blockshift - d.numblocks = int64(nb) - return nil + return d.initRs() } func (d *SPICard) NumberOfBlocks() int64 { @@ -235,118 +93,6 @@ 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) readCID() (CID, error) { - buf := d.buf[len(d.buf)-16:] - if err := d.readRegister(cmdSendCID, buf); err != nil { - return CID{}, err - } - return DecodeCID(buf) -} - -func (d *SPICard) readCSD() (CSD, error) { - buf := d.buf[len(d.buf)-16:] - if err := d.readRegister(cmdSendCSD, buf); err != nil { - return CSD{}, err - } - return DecodeCSD(buf) -} - -func (d *SPICard) readRegister(cmd command, dst []byte) error { - err := d.cmdEnsure0Status(cmd, 0, 0xFF) - if err != nil { - return err - } - if err := d.waitStartBlock(); err != nil { - return err - } - // transfer data - for i := uint16(0); i < 16; i++ { - r, err := d.bus.Transfer(0xFF) - if err != nil { - return err - } - dst[i] = r - } - // skip CRC. - d.bus.Transfer(0xFF) - d.bus.Transfer(0xFF) - d.csEnable(false) - return nil -} - -func (d *SPICard) appCmd(cmd appcommand, arg uint32) (response1, error) { - status, err := d.cmd(cmdAppCmd, 0, 0xFF) - if err != nil { - return status, err - } - return d.cmd(command(cmd), arg, 0xFF) -} - -func (d *SPICard) cmdEnsure0Status(cmd command, arg uint32, precalcCRC byte) error { - status, err := d.cmd(cmd, arg, precalcCRC) - if err != nil { - return err - } - if status != 0 { - return makeResponseError(status) - } - return nil -} - -func (d *SPICard) cmd(cmd command, arg uint32, precalcCRC byte) (response1, error) { - const transmitterBit = 1 << 6 - if cmd >= transmitterBit { - panic("invalid SD command") - } - d.csEnable(true) - - if cmd != cmdStopTransmission { - err := d.waitNotBusy(d.timeout) - if err != nil { - return 0, err - } - } - - // create and send the command - 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], arg) - if precalcCRC != 0 { - buf[5] = precalcCRC - } else { - // CRC and end bit which is always 1. - buf[5] = crc7noshift(buf[:5]) | 1 - } - - err := d.bus.Tx(buf, nil) - if err != nil { - return 0, err - } - if cmd == cmdStopTransmission { - // skip 1 byte - d.bus.Transfer(0xFF) - } - - tm := d.timers[1].setTimeout(d.timeout) - for { - tok, _ := d.bus.Transfer(0xff) - response := response1(tok) - if (response & 0x80) == 0 { - // NOMINAL FUNCTION EXIT HERE - return response, nil - } else if tm.expired() { - break - } - d.yield() - } - println("============== BAD EXIT ================") - d.csEnable(false) - d.bus.Transfer(0xFF) - return 0xFF, errCmdGeneric -} - func (d *SPICard) yield() { time.Sleep(d.wait) } func (d *SPICard) waitNotBusy(timeout time.Duration) error { diff --git a/sd/rustref.go b/sd/rustref.go index 96b129123..985469e0a 100644 --- a/sd/rustref.go +++ b/sd/rustref.go @@ -20,18 +20,22 @@ func (d *SPICard) initRs() error { 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) + println("cmdGoIdle") for retries > 0 { - stat, err := d.card_command(cmdGoIdleState, 0) + stat, err := d.card_command(cmdGoIdleState, 0) // CMD0. if err != nil { if isTimeout(err) { retries-- + println("cmdGoIdle timeout") continue // Try again! } return err @@ -47,9 +51,10 @@ func (d *SPICard) initRs() error { if retries <= 0 { return errNoSDCard } - const enableCRC = false + println("cmdGoIdle done; start cmdCRCON/OFF") + const enableCRC = true if enableCRC { - stat, err := d.card_command(cmdCRCOnOff, 1) + stat, err := d.card_command(cmdCRCOnOff, 1) // CMD59. if err != nil { return err } else if stat != _R1_IDLE_STATE { @@ -57,12 +62,13 @@ func (d *SPICard) initRs() error { } } - tm.setTimeout(d.timeout) + println("cmdCRCON/OFF done; start cmdSendIfCond") + tm.setTimeout(time.Second) for { - stat, err := d.card_command(cmdSendIfCond, 0x1aa) + stat, err := d.card_command(cmdSendIfCond, 0x1AA) // CMD8. if err != nil { return err - } else if stat == _R1_IDLE_STATE || stat == _R1_ILLEGAL_COMMAND { + } else if stat == (_R1_ILLEGAL_COMMAND | _R1_IDLE_STATE) { d.kind = TypeSD1 break } @@ -84,7 +90,9 @@ func (d *SPICard) initRs() error { if d.kind != TypeSD1 { arg = 0x4000_0000 } - for { + println("cmdSendIfCond done; start cmdAppCmd", arg) + tm.setTimeout(time.Second) + for !tm.expired() { stat, err := d.card_acmd(acmdSD_APP_OP_COND, arg) if err != nil { return err @@ -93,7 +101,7 @@ func (d *SPICard) initRs() error { } d.yield() } - + println("cmdAppCmd done; start cmdReadOCR") err := d.updateCSDCID() if err != nil { return err @@ -103,6 +111,7 @@ func (d *SPICard) initRs() error { return nil // Done if not SD2. } + println("discover high capacity") // Discover if card is high capacity. stat, err := d.card_command(cmdReadOCR, 0) if err != nil { @@ -123,6 +132,31 @@ func (d *SPICard) initRs() error { 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 + } + blockshift := d.csd.ReadBlockLenShift() + blocklen := uint16(1) << blockshift + capacity := d.csd.DeviceCapacity() + if blocklen == 0 || capacity < uint64(blocklen) { + return errNoblocks + } + nb := capacity / uint64(blocklen) + if nb > math.MaxUint32 { + return errCardNotSupported + } + d.blockshift = blockshift + d.numblocks = int64(nb) + 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) error { numblocks, err := d.checkBounds(startBlockIdx, len(dst)) @@ -247,6 +281,32 @@ func (d *SPICard) checkBounds(startBlockIdx int64, datalen int) (numblocks int, return numblocks, nil } +func (d *SPICard) read_cid() (csd CID, err error) { + err = d.cmd_read(cmdSendCID, 0, d.buf[:16]) // CMD10. + if err != nil { + return csd, err + } + return DecodeCID(d.buf[:16]) +} + +func (d *SPICard) read_csd() (csd CSD, err error) { + err = d.cmd_read(cmdSendCSD, 0, d.buf[:16]) // CMD9. + if err != nil { + return csd, err + } + return DecodeCSD(d.buf[:16]) +} + +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 { @@ -312,7 +372,7 @@ func (d *SPICard) read_data(data []byte) (err error) { } func (s *SPICard) wait_not_busy() error { - tm := s.timers[0].setTimeout(s.timeout) + tm := s.timers[1].setTimeout(s.timeout) for { tok, err := s.receive() if err != nil { From b31c5ca9c98d648754bb9cb6084c91149453bddf Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 01:32:09 -0300 Subject: [PATCH 13/25] add prints --- sd/rustref.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sd/rustref.go b/sd/rustref.go index 985469e0a..552c74781 100644 --- a/sd/rustref.go +++ b/sd/rustref.go @@ -29,13 +29,11 @@ func (d *SPICard) initRs() error { const maxRetries = 32 retries := maxRetries tm := d.timers[0].setTimeout(2 * time.Second) - println("cmdGoIdle") for retries > 0 { stat, err := d.card_command(cmdGoIdleState, 0) // CMD0. if err != nil { if isTimeout(err) { retries-- - println("cmdGoIdle timeout") continue // Try again! } return err @@ -51,7 +49,6 @@ func (d *SPICard) initRs() error { if retries <= 0 { return errNoSDCard } - println("cmdGoIdle done; start cmdCRCON/OFF") const enableCRC = true if enableCRC { stat, err := d.card_command(cmdCRCOnOff, 1) // CMD59. @@ -62,7 +59,6 @@ func (d *SPICard) initRs() error { } } - println("cmdCRCON/OFF done; start cmdSendIfCond") tm.setTimeout(time.Second) for { stat, err := d.card_command(cmdSendIfCond, 0x1AA) // CMD8. @@ -90,7 +86,6 @@ func (d *SPICard) initRs() error { if d.kind != TypeSD1 { arg = 0x4000_0000 } - println("cmdSendIfCond done; start cmdAppCmd", arg) tm.setTimeout(time.Second) for !tm.expired() { stat, err := d.card_acmd(acmdSD_APP_OP_COND, arg) @@ -101,7 +96,6 @@ func (d *SPICard) initRs() error { } d.yield() } - println("cmdAppCmd done; start cmdReadOCR") err := d.updateCSDCID() if err != nil { return err @@ -111,7 +105,6 @@ func (d *SPICard) initRs() error { return nil // Done if not SD2. } - println("discover high capacity") // Discover if card is high capacity. stat, err := d.card_command(cmdReadOCR, 0) if err != nil { From 677f8ed2976bf0f50768961db977b6dea8cae00e Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 01:33:38 -0300 Subject: [PATCH 14/25] remove prints --- examples/sd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sd/main.go b/examples/sd/main.go index 228fbbb71..331ff9fc2 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -50,7 +50,7 @@ func main() { var buf [512]byte for i := 0; i < 11; i += 1 { - err = sdcard.ReadBlocks(buf[:], 0) + err = sdcard.ReadBlocks(buf[:], int64(i)) if err != nil { println("err reading block", i, ":", err.Error()) continue From 45e207fe2e1618e500ae6ef9b9d6396f198ba8a2 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 01:51:22 -0300 Subject: [PATCH 15/25] remove unused API --- sd/card.go | 33 --------------------------------- sd/responses.go | 17 ----------------- sd/rustref.go | 14 +++++++------- 3 files changed, 7 insertions(+), 57 deletions(-) diff --git a/sd/card.go b/sd/card.go index d64d281b6..7a4feb5e3 100644 --- a/sd/card.go +++ b/sd/card.go @@ -95,39 +95,6 @@ func (d *SPICard) CSD() CSD { return d.csd } func (d *SPICard) yield() { time.Sleep(d.wait) } -func (d *SPICard) waitNotBusy(timeout time.Duration) error { - if _, ok := d.waitToken(timeout, 0xff); ok { - return nil - } - return errBusyTimeout -} - -func (d *SPICard) waitStartBlock() error { - if _, ok := d.waitToken(d.timeout, tokSTART_BLOCK); ok { - return nil - } - return errWaitStartBlock -} - -// waitToken transmits over SPI waiting to read a given byte token. If argument tok -// is 0xff then waitToken will wait for a token that does NOT match 0xff. -func (d *SPICard) waitToken(timeout time.Duration, tok byte) (byte, bool) { - tm := d.timers[1].setTimeout(timeout) - for { - received, err := d.bus.Transfer(0xFF) - if err != nil { - return received, false - } - matchTok := received == tok - if matchTok || (!matchTok && tok == 0xff) { - return received, true - } else if tm.expired() { - return received, false - } - d.yield() - } -} - var timeoutTimer [2]timer type timer struct { diff --git a/sd/responses.go b/sd/responses.go index 7c50763e1..90f74e919 100644 --- a/sd/responses.go +++ b/sd/responses.go @@ -84,23 +84,6 @@ func makeResponseError(status response1) error { } } -// func (c *SPICard) lastR1() r1 { return c.lastr1 } - -// is part of specification but not used in every implementation out there... -func (d *SPICard) readR1() (resp r1, err error) { - first, ok := d.waitToken(d.timeout, 0xff) - if !ok { - return resp, errBusyTimeout - } - err = d.bus.Tx(nil, d.buf[1:6]) - if err != nil { - return resp, err - } - d.buf[0] = first - copy(resp.data[:], d.buf[:6]) - return resp, nil -} - // 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 diff --git a/sd/rustref.go b/sd/rustref.go index 552c74781..76a959d3c 100644 --- a/sd/rustref.go +++ b/sd/rustref.go @@ -200,7 +200,7 @@ func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) error { } d.csEnable(true) defer d.csEnable(false) - + writeTimeout := 2 * d.timeout if numblocks == 1 { _, err = d.card_command(cmdWriteBlock, uint32(startBlockIdx)) if err != nil { @@ -210,7 +210,7 @@ func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) error { if err != nil { return err } - err = d.wait_not_busy() + err = d.wait_not_busy(writeTimeout) if err != nil { return err } @@ -238,7 +238,7 @@ func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) error { for i := 0; i < numblocks; i++ { offset := i * blocksize - err = d.waitNotBusy(d.timeout) + err = d.wait_not_busy(writeTimeout) if err != nil { return err } @@ -248,7 +248,7 @@ func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) error { } } // Stop the multi write operation. - err = d.waitNotBusy(d.timeout) + err = d.wait_not_busy(writeTimeout) if err != nil { return err } @@ -310,7 +310,7 @@ func (d *SPICard) card_acmd(acmd appcommand, args uint32) (uint8, error) { func (d *SPICard) card_command(cmd command, args uint32) (uint8, error) { const transmitterBit = 1 << 6 - err := d.wait_not_busy() + err := d.wait_not_busy(d.timeout) if err != nil { return 0, err } @@ -364,8 +364,8 @@ func (d *SPICard) read_data(data []byte) (err error) { return nil } -func (s *SPICard) wait_not_busy() error { - tm := s.timers[1].setTimeout(s.timeout) +func (s *SPICard) wait_not_busy(timeout time.Duration) error { + tm := s.timers[1].setTimeout(timeout) for { tok, err := s.receive() if err != nil { From e6907db19ee7df1f1fa6d15a1bd60b09d7534524 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 20:09:27 -0300 Subject: [PATCH 16/25] add BlockDevice --- sd/blockdevice.go | 150 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 sd/blockdevice.go diff --git a/sd/blockdevice.go b/sd/blockdevice.go new file mode 100644 index 000000000..680a7fa3e --- /dev/null +++ b/sd/blockdevice.go @@ -0,0 +1,150 @@ +package sd + +import ( + "errors" + "math/bits" +) + +var ( + errNegativeOffset = errors.New("sd: negative offset") +) + +type Card interface { + WriteBlocks(data []byte, startBlockIdx int64) error + ReadBlocks(dst []byte, startBlockIdx int64) error + EraseBlockSize() int64 + EraseBlocks(start, len int64) error +} + +func NewBlockDevice(card Card, blockSize int, numBlocks int64) *BlockDevice { + if card == nil || blockSize <= 0 { + panic("invalid arguments") + } + tz := bits.TrailingZeros(uint(blockSize)) + if blockSize>>tz != 1 { + panic("blockSize must be a power of 2") + } + bd := &BlockDevice{ + card: card, + blockbuf: make([]byte, blockSize), + blockshift: tz, + blockmask: (1 << tz) - 1, + numblocks: numBlocks, + } + return bd +} + +// BlockDevice implements tinyfs.BlockDevice interface. +type BlockDevice struct { + card Card + blockbuf []byte + blockshift int + blockmask int64 + numblocks int64 +} + +func (bd *BlockDevice) moduloBlockSize(n int64) int64 { + return n &^ bd.blockmask +} + +func (bd *BlockDevice) divideBlockSize(n int64) int64 { + return n >> bd.blockshift +} + +func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if off < 0 { + return 0, errNegativeOffset + } + blockSize := len(bd.blockbuf) + blockIdx := bd.divideBlockSize(off) + blockOff := bd.moduloBlockSize(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++ + } + + remaining := len(p) - n + if remaining >= blockSize { + // 1 or more full blocks case. + endOffset := remaining - int(bd.moduloBlockSize(int64(remaining))) + err = bd.card.ReadBlocks(p[:endOffset], blockIdx) + if err != nil { + return n, err + } + p = p[endOffset:] + n += endOffset + blockIdx += int64(endOffset / blockSize) + } + + 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 +} + +func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if off < 0 { + return 0, errNegativeOffset + } + blockSize := len(bd.blockbuf) + blockIdx := bd.divideBlockSize(off) + blockOff := bd.moduloBlockSize(off) + if blockOff != 0 { + // Non-aligned first block case. + if err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + n += copy(bd.blockbuf[blockOff:], p) + if err := bd.card.WriteBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + p = p[n:] + blockIdx++ + } + + remaining := len(p) - n + if remaining >= blockSize { + // 1 or more full blocks case. + endOffset := remaining - int(bd.moduloBlockSize(int64(remaining))) + err = bd.card.WriteBlocks(p[:endOffset], blockIdx) + if err != nil { + return n, err + } + p = p[endOffset:] + n += endOffset + blockIdx += int64(endOffset / blockSize) + } + + if len(p) > 0 { + // Non-aligned last block case. + if err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + n += copy(bd.blockbuf, p) + if err := bd.card.WriteBlocks(bd.blockbuf, blockIdx); err != nil { + return n, err + } + } + return n, nil +} + +func (bd *BlockDevice) Size() int64 { + return int64(len(bd.blockbuf)) * bd.numblocks +} + +func (bd *BlockDevice) EraseBlocks(start, len int64) error { + return bd.card.EraseBlocks(start, len) +} + +func (bd *BlockDevice) EraseBlockSize() int64 { + return bd.card.EraseBlockSize() +} From c5ff13ad236dbbe3713770d554af31422099af74 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 21:11:49 -0300 Subject: [PATCH 17/25] implement Card interface --- sd/blockdevice.go | 29 ++++++++++++++++------------- sd/card.go | 2 -- sd/rustref.go | 25 +++++++++++++++++++------ 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/sd/blockdevice.go b/sd/blockdevice.go index 680a7fa3e..7311f166d 100644 --- a/sd/blockdevice.go +++ b/sd/blockdevice.go @@ -9,20 +9,22 @@ var ( errNegativeOffset = errors.New("sd: negative offset") ) +// Compile time guarantee of interface implementation. +var _ Card = (*SPICard)(nil) + type Card interface { WriteBlocks(data []byte, startBlockIdx int64) error ReadBlocks(dst []byte, startBlockIdx int64) error - EraseBlockSize() int64 EraseBlocks(start, len int64) error } -func NewBlockDevice(card Card, blockSize int, numBlocks int64) *BlockDevice { - if card == nil || blockSize <= 0 { - panic("invalid arguments") +func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSize int64) (*BlockDevice, error) { + if card == nil || blockSize <= 0 || eraseBlockSize <= 0 || numBlocks <= 0 { + return nil, errors.New("invalid argument(s)") } tz := bits.TrailingZeros(uint(blockSize)) if blockSize>>tz != 1 { - panic("blockSize must be a power of 2") + return nil, errors.New("blockSize must be a power of 2") } bd := &BlockDevice{ card: card, @@ -31,20 +33,21 @@ func NewBlockDevice(card Card, blockSize int, numBlocks int64) *BlockDevice { blockmask: (1 << tz) - 1, numblocks: numBlocks, } - return bd + return bd, nil } // BlockDevice implements tinyfs.BlockDevice interface. type BlockDevice struct { - card Card - blockbuf []byte - blockshift int - blockmask int64 - numblocks int64 + card Card + blockbuf []byte + blockshift int + blockmask int64 + numblocks int64 + eraseBlockSize int64 } func (bd *BlockDevice) moduloBlockSize(n int64) int64 { - return n &^ bd.blockmask + return n & bd.blockmask } func (bd *BlockDevice) divideBlockSize(n int64) int64 { @@ -146,5 +149,5 @@ func (bd *BlockDevice) EraseBlocks(start, len int64) error { } func (bd *BlockDevice) EraseBlockSize() int64 { - return bd.card.EraseBlockSize() + return bd.eraseBlockSize } diff --git a/sd/card.go b/sd/card.go index 7a4feb5e3..00028898a 100644 --- a/sd/card.go +++ b/sd/card.go @@ -34,8 +34,6 @@ type SPICard struct { bus drivers.SPI cs digitalPinout bufcmd [6]byte - buf [512]byte - bufTok [1]byte kind CardKind cid CID csd CSD diff --git a/sd/rustref.go b/sd/rustref.go index 76a959d3c..5cf7bb5c4 100644 --- a/sd/rustref.go +++ b/sd/rustref.go @@ -189,6 +189,10 @@ func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) error { panic("unreachable numblocks<=0") } +func (d *SPICard) EraseBlocks(startBlock, endBlock 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) error { numblocks, err := d.checkBounds(startBlockIdx, len(data)) @@ -274,20 +278,26 @@ func (d *SPICard) checkBounds(startBlockIdx int64, datalen int) (numblocks int, return numblocks, nil } -func (d *SPICard) read_cid() (csd CID, err error) { - err = d.cmd_read(cmdSendCID, 0, d.buf[:16]) // CMD10. +func (d *SPICard) read_cid() (cid CID, err error) { + err = d.cmd_read(cmdSendCID, 0, d.cid.data[:16]) // CMD10. if err != nil { - return csd, err + return cid, err + } + if !d.cid.IsValid() { + return cid, errBadCSDCID } - return DecodeCID(d.buf[:16]) + return d.cid, nil } func (d *SPICard) read_csd() (csd CSD, err error) { - err = d.cmd_read(cmdSendCSD, 0, d.buf[:16]) // CMD9. + err = d.cmd_read(cmdSendCSD, 0, d.csd.data[:16]) // CMD9. if err != nil { return csd, err } - return DecodeCSD(d.buf[:16]) + if !d.csd.IsValid() { + return csd, errBadCSDCID + } + return d.csd, nil } func (d *SPICard) cmd_read(cmd command, args uint32, buf []byte) error { @@ -322,6 +332,9 @@ func (d *SPICard) card_command(cmd command, args uint32) (uint8, error) { 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. } From cb2ca239f06152304819bc0fc535e552394d32bb Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 21:25:22 -0300 Subject: [PATCH 18/25] rename EraseBlocks to EraseSectors --- sd/blockdevice.go | 39 ++++++++++++++++++++++++++++----------- sd/card.go | 37 +++++++++++++++---------------------- sd/rustref.go | 2 +- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/sd/blockdevice.go b/sd/blockdevice.go index 7311f166d..f3c6d3ac8 100644 --- a/sd/blockdevice.go +++ b/sd/blockdevice.go @@ -13,13 +13,19 @@ var ( var _ Card = (*SPICard)(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) 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) error - EraseBlocks(start, len int64) error + // EraseBlocks erases + EraseSectors(startBlockIdx, numBlocks int64) error } -func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSize int64) (*BlockDevice, error) { - if card == nil || blockSize <= 0 || eraseBlockSize <= 0 || numBlocks <= 0 { +// NewBlockDevice creates a new BlockDevice from a Card. +func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSizeInBytes int64) (*BlockDevice, error) { + if card == nil || blockSize <= 0 || eraseBlockSizeInBytes <= 0 || numBlocks <= 0 { return nil, errors.New("invalid argument(s)") } tz := bits.TrailingZeros(uint(blockSize)) @@ -27,16 +33,17 @@ func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSize int64) ( return nil, errors.New("blockSize must be a power of 2") } bd := &BlockDevice{ - card: card, - blockbuf: make([]byte, blockSize), - blockshift: tz, - blockmask: (1 << tz) - 1, - numblocks: numBlocks, + card: card, + blockbuf: make([]byte, blockSize), + blockshift: tz, + blockmask: (1 << tz) - 1, + numblocks: int64(numBlocks), + eraseBlockSize: eraseBlockSizeInBytes, } return bd, nil } -// BlockDevice implements tinyfs.BlockDevice interface. +// BlockDevice implements tinyfs.BlockDevice interface for an [sd.Card] type. type BlockDevice struct { card Card blockbuf []byte @@ -54,6 +61,7 @@ func (bd *BlockDevice) divideBlockSize(n int64) int64 { return n >> bd.blockshift } +// 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 @@ -94,6 +102,7 @@ func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) { 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 @@ -140,14 +149,22 @@ func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) { return n, nil } +// Size returns the number of bytes in this block device. func (bd *BlockDevice) Size() int64 { return int64(len(bd.blockbuf)) * bd.numblocks } -func (bd *BlockDevice) EraseBlocks(start, len int64) error { - return bd.card.EraseBlocks(start, len) +// 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.EraseSectors(startEraseBlockIdx, len) } +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +// It must be a power of two, and may be as small as 1. A typical size is 4096. func (bd *BlockDevice) EraseBlockSize() int64 { return bd.eraseBlockSize } diff --git a/sd/card.go b/sd/card.go index 00028898a..1929227c7 100644 --- a/sd/card.go +++ b/sd/card.go @@ -13,10 +13,6 @@ 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") - errCmd8 = errors.New("sd:cmd8") - errCmdOCR = errors.New("sd:cmd_ocr") - errCmdBlkLen = errors.New("sd:cmd_blklen") - errAcmdAppCond = errors.New("sd:acmd_appOrCond") 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") @@ -25,28 +21,27 @@ var ( errBusyTimeout = errors.New("sd:busy card timeout") errOOB = errors.New("sd:oob block access") errNoblocks = errors.New("sd:no readable blocks") - errCmdGeneric = errors.New("sd:command error") ) -type digitalPinout func(b bool) +type digitalPinout = func(b bool) type SPICard struct { - bus drivers.SPI - cs digitalPinout - bufcmd [6]byte - kind CardKind - cid CID - csd CSD - lastCRC uint16 + bus drivers.SPI + cs digitalPinout + + timers [2]timer + numblocks int64 + timeout time.Duration + wait time.Duration + // Card Identification Register. + cid CID + // Card Specific Register. + csd CSD + bufcmd [6]byte + kind CardKind // shift to calculate blocksize, taken from CSD. blockshift uint8 - timers [2]timer - numblocks int64 - timeout time.Duration - wait time.Duration - // relative card address. - rca uint32 - lastr1 r1 + lastCRC uint16 } func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { @@ -93,8 +88,6 @@ func (d *SPICard) CSD() CSD { return d.csd } func (d *SPICard) yield() { time.Sleep(d.wait) } -var timeoutTimer [2]timer - type timer struct { deadline time.Time } diff --git a/sd/rustref.go b/sd/rustref.go index 5cf7bb5c4..f295441c0 100644 --- a/sd/rustref.go +++ b/sd/rustref.go @@ -189,7 +189,7 @@ func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) error { panic("unreachable numblocks<=0") } -func (d *SPICard) EraseBlocks(startBlock, endBlock int64) error { +func (d *SPICard) EraseSectors(startSector, numberSectors int64) error { return errors.New("sd:erase not implemented") } From 0d80962dc81c2442d9098105f6148080dc0c0c73 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 15 Jan 2024 23:23:08 -0300 Subject: [PATCH 19/25] add blkIdxer --- sd/blockdevice.go | 89 +++++++++++++++++++++++++++++++++++------------ sd/card.go | 15 ++++---- sd/definitions.go | 31 ++++++++++------- sd/rustref.go | 45 +++++++++++------------- 4 files changed, 113 insertions(+), 67 deletions(-) diff --git a/sd/blockdevice.go b/sd/blockdevice.go index f3c6d3ac8..0003ef048 100644 --- a/sd/blockdevice.go +++ b/sd/blockdevice.go @@ -2,6 +2,7 @@ package sd import ( "errors" + "io" "math/bits" ) @@ -11,6 +12,8 @@ var ( // 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. @@ -19,8 +22,8 @@ type Card interface { // 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) error - // EraseBlocks erases - EraseSectors(startBlockIdx, numBlocks int64) error + // EraseSectors erases sectors starting at startSectorIdx to startSectorIdx+numSectors. + EraseSectors(startSectorIdx, numSectors int64) error } // NewBlockDevice creates a new BlockDevice from a Card. @@ -28,15 +31,14 @@ func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSizeInBytes i if card == nil || blockSize <= 0 || eraseBlockSizeInBytes <= 0 || numBlocks <= 0 { return nil, errors.New("invalid argument(s)") } - tz := bits.TrailingZeros(uint(blockSize)) - if blockSize>>tz != 1 { - return nil, errors.New("blockSize must be a power of 2") + blk, err := makeBlockIndexer(blockSize) + if err != nil { + return nil, err } bd := &BlockDevice{ card: card, blockbuf: make([]byte, blockSize), - blockshift: tz, - blockmask: (1 << tz) - 1, + blk: blk, numblocks: int64(numBlocks), eraseBlockSize: eraseBlockSizeInBytes, } @@ -47,30 +49,22 @@ func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSizeInBytes i type BlockDevice struct { card Card blockbuf []byte - blockshift int - blockmask int64 + blk blkIdxer numblocks int64 eraseBlockSize int64 } -func (bd *BlockDevice) moduloBlockSize(n int64) int64 { - return n & bd.blockmask -} - -func (bd *BlockDevice) divideBlockSize(n int64) int64 { - return n >> bd.blockshift -} - // 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 } blockSize := len(bd.blockbuf) - blockIdx := bd.divideBlockSize(off) - blockOff := bd.moduloBlockSize(off) + blockIdx := bd.blk.idx(off) + blockOff := bd.blk.off(off) if blockOff != 0 { // Non-aligned first block case. + println("read", len(bd.blockbuf), "bytes from block", blockIdx) if err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { return n, err } @@ -82,7 +76,7 @@ func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) { remaining := len(p) - n if remaining >= blockSize { // 1 or more full blocks case. - endOffset := remaining - int(bd.moduloBlockSize(int64(remaining))) + endOffset := remaining - int(bd.blk.off(int64(remaining))) err = bd.card.ReadBlocks(p[:endOffset], blockIdx) if err != nil { return n, err @@ -108,8 +102,8 @@ func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) { return 0, errNegativeOffset } blockSize := len(bd.blockbuf) - blockIdx := bd.divideBlockSize(off) - blockOff := bd.moduloBlockSize(off) + 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 { @@ -126,7 +120,7 @@ func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) { remaining := len(p) - n if remaining >= blockSize { // 1 or more full blocks case. - endOffset := remaining - int(bd.moduloBlockSize(int64(remaining))) + endOffset := remaining - int(bd.blk.off(int64(remaining))) err = bd.card.WriteBlocks(p[:endOffset], blockIdx) if err != nil { return n, err @@ -168,3 +162,52 @@ func (bd *BlockDevice) EraseBlocks(startEraseBlockIdx, len int64) error { func (bd *BlockDevice) EraseBlockSize() int64 { return bd.eraseBlockSize } + +// blkIdxer is a helper for calculating block indexes 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.go b/sd/card.go index 1929227c7..f014ed56c 100644 --- a/sd/card.go +++ b/sd/card.go @@ -29,19 +29,18 @@ type SPICard struct { bus drivers.SPI cs digitalPinout - timers [2]timer - numblocks int64 - timeout time.Duration - wait time.Duration + timers [2]timer + timeout time.Duration + wait time.Duration // Card Identification Register. cid CID // Card Specific Register. csd CSD bufcmd [6]byte kind CardKind - // shift to calculate blocksize, taken from CSD. - blockshift uint8 - lastCRC uint16 + // block indexing helper based on block size. + blk blkIdxer + lastCRC uint16 } func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard { @@ -77,7 +76,7 @@ func (d *SPICard) Init() error { } func (d *SPICard) NumberOfBlocks() int64 { - return d.numblocks + return d.csd.NumberOfBlocks() } // CID returns a copy of the Card Identification Register value last read. diff --git a/sd/definitions.go b/sd/definitions.go index 0c4e3195f..c5df57030 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -154,7 +154,7 @@ func (c *CSD) CommandClasses() CommandClasses { } // ReadBlockLen returns the Max Read Data Block Length in bytes. -func (c *CSD) ReadBlockLen() uint16 { return 1 << c.ReadBlockLenShift() } +func (c *CSD) ReadBlockLen() int64 { return 1 << c.ReadBlockLenShift() } func (c *CSD) ReadBlockLenShift() uint8 { return c.data[5] & 0x0F } // AllowsReadBlockPartial should always return true. Indicates that @@ -185,7 +185,14 @@ 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 *CSD) EraseSectorSizeInBlocks() uint8 { +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)) } @@ -201,8 +208,8 @@ func (c *CSD) WriteProtectGroupSizeInSectors() uint8 { return 1 + (c.data[11] & 0b111_1111) } -// WriteBlockLength represents maximum write data block length in bytes. -func (c *CSD) WriteBlockLength() uint16 { +// 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)) } @@ -226,11 +233,11 @@ 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 uint64) { +func (c *CSD) DeviceCapacity() (size int64) { switch c.csdStructure() { case 0: v1 := c.MustV1() - size = uint64(v1.DeviceCapacity()) + size = int64(v1.DeviceCapacity()) case 1: v2 := c.MustV2() size = v2.DeviceCapacity() @@ -239,20 +246,20 @@ func (c *CSD) DeviceCapacity() (size uint64) { } // NumberOfBlocks returns amount of readable blocks in the device given by Capacity/ReadBlockLength. -func (c *CSD) NumberOfBlocks() (numBlocks uint64) { +func (c *CSD) NumberOfBlocks() (numBlocks int64) { rblocks := c.ReadBlockLen() if rblocks == 0 { return 0 } - return c.DeviceCapacity() / uint64(rblocks) + 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() uint64 { +func (c *CSDv2) DeviceCapacity() int64 { csize := c.csize() - return uint64(csize) * 512_000 + return int64(csize) * 512_000 } func (c *CSDv2) csize() uint32 { @@ -311,7 +318,7 @@ 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)", c.DeviceCapacity(), 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) @@ -322,7 +329,7 @@ func (c *CSD) appendf(b []byte, delim byte) []byte { 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.WriteBlockLength()), 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:"...) diff --git a/sd/rustref.go b/sd/rustref.go index f295441c0..513088d13 100644 --- a/sd/rustref.go +++ b/sd/rustref.go @@ -5,7 +5,6 @@ import ( "errors" "io" "math" - "math/bits" "time" ) @@ -135,18 +134,11 @@ func (d *SPICard) updateCSDCID() (err error) { if err != nil { return err } - blockshift := d.csd.ReadBlockLenShift() - blocklen := uint16(1) << blockshift - capacity := d.csd.DeviceCapacity() - if blocklen == 0 || capacity < uint64(blocklen) { - return errNoblocks - } - nb := capacity / uint64(blocklen) - if nb > math.MaxUint32 { - return errCardNotSupported + blklen := d.csd.ReadBlockLen() + d.blk, err = makeBlockIndexer(int(blklen)) + if err != nil { + return err } - d.blockshift = blockshift - d.numblocks = int64(nb) return nil } @@ -160,17 +152,17 @@ func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) error { startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards. } d.csEnable(true) - defer d.csEnable(false) + defer d.endTx() if numblocks == 1 { - _, err = d.card_command(cmdReadSingleBlock, uint32(startBlockIdx)) + _, err := d.card_command(cmdReadSingleBlock, uint32(startBlockIdx)) if err != nil { return err } return d.read_data(dst) } else if numblocks > 1 { - blocksize := 1 << d.blockshift + blocksize := int(d.blk.size()) _, err = d.card_command(cmdReadMultipleBlock, uint32(startBlockIdx)) if err != nil { return err @@ -183,12 +175,16 @@ func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) error { return err } } - _, err = d.card_command(cmdStopTransmission, 0) - return err + return nil } panic("unreachable numblocks<=0") } +func (d *SPICard) endTx() { + d.card_command(cmdStopTransmission, 0) + d.csEnable(false) +} + func (d *SPICard) EraseSectors(startSector, numberSectors int64) error { return errors.New("sd:erase not implemented") } @@ -203,7 +199,8 @@ func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) error { startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards. } d.csEnable(true) - defer d.csEnable(false) + defer d.endTx() + writeTimeout := 2 * d.timeout if numblocks == 1 { _, err = d.card_command(cmdWriteBlock, uint32(startBlockIdx)) @@ -234,7 +231,7 @@ func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) error { } else if numblocks > 1 { // Start multi block write. - blocksize := 1 << d.blockshift + blocksize := 1 << d.blk.size() _, err = d.card_command(cmdWriteMultipleBlock, uint32(startBlockIdx)) if err != nil { return err @@ -262,16 +259,15 @@ func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) error { } func (d *SPICard) checkBounds(startBlockIdx int64, datalen int) (numblocks int, err error) { - if startBlockIdx >= d.numblocks { + if startBlockIdx >= d.NumberOfBlocks() { return 0, errOOB } else if startBlockIdx > math.MaxUint32 { return 0, errCardNotSupported } - tz := bits.TrailingZeros(uint(datalen)) - if tz < int(d.blockshift) { + if d.blk.off(int64(datalen)) > 0 { return 0, errNeedBlockLenMultiple } - numblocks = datalen >> d.blockshift + numblocks = int(d.blk.idx(int64(datalen))) if numblocks == 0 { return 0, io.ErrShortBuffer } @@ -353,7 +349,8 @@ func (d *SPICard) card_command(cmd command, args uint32) (uint8, error) { func (d *SPICard) read_data(data []byte) (err error) { var status uint8 - for { + tm := d.timers[1].setTimeout(d.timeout) + for !tm.expired() { status, err = d.receive() if err != nil { return err From 964364005dc85271cf663d46840208b4089d62ac Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 16 Jan 2024 00:46:50 -0300 Subject: [PATCH 20/25] working BlockDevice --- examples/sd/main.go | 67 +++++++++++++++++++--- sd/blockdevice.go | 74 +++++++++++++++---------- sd/rustref.go | 132 +++++++++++++++++++++++++------------------- 3 files changed, 177 insertions(+), 96 deletions(-) diff --git a/examples/sd/main.go b/examples/sd/main.go index 331ff9fc2..e80a3c2e1 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -48,14 +48,65 @@ func main() { cid := sdcard.CID() fmt.Printf("name=%s\ncsd=\n%s\n", cid.ProductName(), csd.String()) - var buf [512]byte - for i := 0; i < 11; i += 1 { - err = sdcard.ReadBlocks(buf[:], int64(i)) - if err != nil { - println("err reading block", i, ":", err.Error()) - continue + const placeholderEraseSectorSize = 512 + bd, err := sd.NewBlockDevice(sdcard, int(csd.ReadBlockLen()), csd.NumberOfBlocks(), placeholderEraseSectorSize) + 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 } - expectCRC := sd.CRC16(buf[:]) - fmt.Printf("block %d theircrc=%#x ourcrc=%#x:\n\t%#x\n", i, sdcard.LastReadCRC(), expectCRC, buf[:]) + 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/blockdevice.go b/sd/blockdevice.go index 0003ef048..2891be519 100644 --- a/sd/blockdevice.go +++ b/sd/blockdevice.go @@ -18,10 +18,10 @@ 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) error + 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) error + ReadBlocks(dst []byte, startBlockIdx int64) (int, error) // EraseSectors erases sectors starting at startSectorIdx to startSectorIdx+numSectors. EraseSectors(startSectorIdx, numSectors int64) error } @@ -59,13 +59,12 @@ func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) { if off < 0 { return 0, errNegativeOffset } - blockSize := len(bd.blockbuf) + blockIdx := bd.blk.idx(off) blockOff := bd.blk.off(off) if blockOff != 0 { // Non-aligned first block case. - println("read", len(bd.blockbuf), "bytes from block", blockIdx) - if err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + if _, err = bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { return n, err } n += copy(p, bd.blockbuf[blockOff:]) @@ -73,22 +72,22 @@ func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) { blockIdx++ } - remaining := len(p) - n - if remaining >= blockSize { + fullBlocksToRead := bd.blk.idx(int64(len(p))) + if fullBlocksToRead > 0 { // 1 or more full blocks case. - endOffset := remaining - int(bd.blk.off(int64(remaining))) - err = bd.card.ReadBlocks(p[:endOffset], blockIdx) + endOffset := fullBlocksToRead * bd.blk.size() + ngot, err := bd.card.ReadBlocks(p[:endOffset], blockIdx) if err != nil { - return n, err + return n + ngot, err } p = p[endOffset:] - n += endOffset - blockIdx += int64(endOffset / blockSize) + n += ngot + blockIdx += fullBlocksToRead } if len(p) > 0 { // Non-aligned last block case. - if err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { return n, err } n += copy(p, bd.blockbuf) @@ -101,51 +100,66 @@ func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) { if off < 0 { return 0, errNegativeOffset } - blockSize := len(bd.blockbuf) + 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 { + if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { return n, err } - n += copy(bd.blockbuf[blockOff:], p) - if err := bd.card.WriteBlocks(bd.blockbuf, blockIdx); err != nil { + 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 } - p = p[n:] + n += nexpect + p = p[nexpect:] blockIdx++ } - remaining := len(p) - n - if remaining >= blockSize { + fullBlocksToWrite := bd.blk.idx(int64(len(p))) + if fullBlocksToWrite > 0 { // 1 or more full blocks case. - endOffset := remaining - int(bd.blk.off(int64(remaining))) - err = bd.card.WriteBlocks(p[:endOffset], blockIdx) + 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[endOffset:] - n += endOffset - blockIdx += int64(endOffset / blockSize) + p = p[ngot:] + blockIdx += fullBlocksToWrite } if len(p) > 0 { // Non-aligned last block case. - if err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { + if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil { return n, err } - n += copy(bd.blockbuf, p) - if err := bd.card.WriteBlocks(bd.blockbuf, blockIdx); err != nil { + 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 int64(len(bd.blockbuf)) * bd.numblocks + 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 @@ -163,7 +177,7 @@ func (bd *BlockDevice) EraseBlockSize() int64 { return bd.eraseBlockSize } -// blkIdxer is a helper for calculating block indexes and offsets. +// blkIdxer is a helper for calculating block indices and offsets. type blkIdxer struct { blockshift int64 blockmask int64 diff --git a/sd/rustref.go b/sd/rustref.go index 513088d13..3c6880b3d 100644 --- a/sd/rustref.go +++ b/sd/rustref.go @@ -143,121 +143,136 @@ func (d *SPICard) updateCSDCID() (err error) { } // 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) error { +func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) { numblocks, err := d.checkBounds(startBlockIdx, len(dst)) if err != nil { - return err + return 0, err } if d.kind != TypeSDHC { startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards. } + d.csEnable(true) - defer d.endTx() + defer d.csEnable(false) if numblocks == 1 { - _, err := d.card_command(cmdReadSingleBlock, uint32(startBlockIdx)) - if err != nil { - return err - } - return d.read_data(dst) + 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()) - _, err = d.card_command(cmdReadMultipleBlock, uint32(startBlockIdx)) - if err != nil { - return err - } - for i := 0; i < numblocks; i++ { - offset := i * blocksize - err = d.read_data(dst[offset : offset+blocksize]) + dataoff := i * blocksize + d.csEnable(true) + _, err := d.read_block_single(dst[dataoff:dataoff+blocksize], int64(i)+startBlockIdx) if err != nil { - return err + return dataoff, err } + d.csEnable(false) } - return nil + return len(dst), nil } panic("unreachable numblocks<=0") } -func (d *SPICard) endTx() { - d.card_command(cmdStopTransmission, 0) - d.csEnable(false) -} - func (d *SPICard) EraseSectors(startSector, numberSectors 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) error { +func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) (int, error) { numblocks, err := d.checkBounds(startBlockIdx, len(data)) if err != nil { - return err + return 0, err } if d.kind != TypeSDHC { startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards. } d.csEnable(true) - defer d.endTx() + defer d.csEnable(false) writeTimeout := 2 * d.timeout if numblocks == 1 { - _, err = d.card_command(cmdWriteBlock, uint32(startBlockIdx)) - if err != nil { - return err - } - err = d.write_data(tokSTART_BLOCK, data) - if err != nil { - return err - } - err = d.wait_not_busy(writeTimeout) - if err != nil { - return err - } - status, err := d.card_command(cmdSendStatus, 0) - if err != nil { - return err - } else if status != 0 { - return makeResponseError(response1(status)) - } - status, err = d.receive() - if err != nil { - return err - } else if status != 0 { - return errWrite - } - return nil + return d.write_block_single(data, startBlockIdx) } else if numblocks > 1 { // Start multi block write. - blocksize := 1 << d.blk.size() + blocksize := int(d.blk.size()) _, err = d.card_command(cmdWriteMultipleBlock, uint32(startBlockIdx)) if err != nil { - return err + return 0, err } for i := 0; i < numblocks; i++ { offset := i * blocksize err = d.wait_not_busy(writeTimeout) if err != nil { - return err + return 0, err } err = d.write_data(tokWRITE_MULT, data[offset:offset+blocksize]) if err != nil { - return err + return 0, err } } // Stop the multi write operation. err = d.wait_not_busy(writeTimeout) if err != nil { - return err + return 0, err + } + err = d.send(tokSTOP_TRAN) + if err != nil { + return 0, err } - return d.send(tokSTOP_TRAN) + _, 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 @@ -354,9 +369,10 @@ func (d *SPICard) read_data(data []byte) (err error) { status, err = d.receive() if err != nil { return err - } - if status != 0xff { + } else if status != 0xff { break + } else if tm.expired() { + return errReadTimeout } d.yield() } From 4d0d8e9c140d0a311b475c809292880873d5fe33 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 23 Jan 2024 20:16:08 -0300 Subject: [PATCH 21/25] delete rustref.go; add readme to sd --- sd/README.md | 2 + sd/card.go | 452 +++++++++++++++++++++++++++++++++++++++++++++++++- sd/rustref.go | 447 ------------------------------------------------- 3 files changed, 450 insertions(+), 451 deletions(-) create mode 100644 sd/README.md delete mode 100644 sd/rustref.go diff --git a/sd/README.md b/sd/README.md new file mode 100644 index 000000000..355771cd1 --- /dev/null +++ b/sd/README.md @@ -0,0 +1,2 @@ +## `sd` package + diff --git a/sd/card.go b/sd/card.go index f014ed56c..8d82cfe19 100644 --- a/sd/card.go +++ b/sd/card.go @@ -1,7 +1,10 @@ package sd import ( + "encoding/binary" "errors" + "io" + "math" "time" "tinygo.org/x/drivers" @@ -62,10 +65,6 @@ func (c *SPICard) setTimeout(timeout time.Duration) { c.wait = timeout / 512 } -func (c *SPICard) csEnable(b bool) { - c.cs(!b) -} - // LastReadCRC returns the CRC for the last ReadBlock operation. func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC } @@ -99,3 +98,448 @@ func (t *timer) setTimeout(timeout time.Duration) *timer { 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) EraseSectors(startSector, numberSectors 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) +} diff --git a/sd/rustref.go b/sd/rustref.go deleted file mode 100644 index 3c6880b3d..000000000 --- a/sd/rustref.go +++ /dev/null @@ -1,447 +0,0 @@ -package sd - -import ( - "encoding/binary" - "errors" - "io" - "math" - "time" -) - -// 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) EraseSectors(startSector, numberSectors 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 -} From e14fbf6d3dc04083d638e09d52ce8cd0adfe36fb Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 23 Jan 2024 20:21:27 -0300 Subject: [PATCH 22/25] add sd/README.md --- sd/README.md | 9 +++++++++ sd/{card.go => spicard.go} | 0 2 files changed, 9 insertions(+) rename sd/{card.go => spicard.go} (100%) diff --git a/sd/README.md b/sd/README.md index 355771cd1..54c71b2ae 100644 --- a/sd/README.md +++ b/sd/README.md @@ -1,2 +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/card.go b/sd/spicard.go similarity index 100% rename from sd/card.go rename to sd/spicard.go From 8de5ab7c64f56e95e5413254c641d2d4879d2063 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 23 Jan 2024 20:25:58 -0300 Subject: [PATCH 23/25] sd: backtrack on EraseSectors, stick to block nomenclature --- sd/blockdevice.go | 6 +++--- sd/spicard.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sd/blockdevice.go b/sd/blockdevice.go index 2891be519..2ee72b54d 100644 --- a/sd/blockdevice.go +++ b/sd/blockdevice.go @@ -22,8 +22,8 @@ type Card interface { // 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) - // EraseSectors erases sectors starting at startSectorIdx to startSectorIdx+numSectors. - EraseSectors(startSectorIdx, numSectors int64) error + // EraseBlocks erases blocks starting at startBlockIdx to startBlockIdx+numBlocks. + EraseBlocks(startBlock, numBlocks int64) error } // NewBlockDevice creates a new BlockDevice from a Card. @@ -167,7 +167,7 @@ func (bd *BlockDevice) BlockSize() int64 { // 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.EraseSectors(startEraseBlockIdx, len) + return bd.card.EraseBlocks(startEraseBlockIdx, len) } // EraseBlockSize returns the smallest erasable area on this particular chip diff --git a/sd/spicard.go b/sd/spicard.go index 8d82cfe19..89813572a 100644 --- a/sd/spicard.go +++ b/sd/spicard.go @@ -267,7 +267,7 @@ func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) { panic("unreachable numblocks<=0") } -func (d *SPICard) EraseSectors(startSector, numberSectors int64) error { +func (d *SPICard) EraseBlocks(startBlock, numberOfBlocks int64) error { return errors.New("sd:erase not implemented") } From 37ae0ad5b8869576696b60c292e23f680d0a3a73 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 23 Jan 2024 20:33:05 -0300 Subject: [PATCH 24/25] remove remnants of sector size --- sd/blockdevice.go | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/sd/blockdevice.go b/sd/blockdevice.go index 2ee72b54d..f3cabac07 100644 --- a/sd/blockdevice.go +++ b/sd/blockdevice.go @@ -27,8 +27,8 @@ type Card interface { } // NewBlockDevice creates a new BlockDevice from a Card. -func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSizeInBytes int64) (*BlockDevice, error) { - if card == nil || blockSize <= 0 || eraseBlockSizeInBytes <= 0 || numBlocks <= 0 { +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) @@ -36,22 +36,20 @@ func NewBlockDevice(card Card, blockSize int, numBlocks, eraseBlockSizeInBytes i return nil, err } bd := &BlockDevice{ - card: card, - blockbuf: make([]byte, blockSize), - blk: blk, - numblocks: int64(numBlocks), - eraseBlockSize: eraseBlockSizeInBytes, + 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 - eraseBlockSize int64 + card Card + blockbuf []byte + blk blkIdxer + numblocks int64 } // ReadAt implements [io.ReadAt] interface for an SD card. @@ -170,13 +168,6 @@ func (bd *BlockDevice) EraseBlocks(startEraseBlockIdx, len int64) error { return bd.card.EraseBlocks(startEraseBlockIdx, len) } -// EraseBlockSize returns the smallest erasable area on this particular chip -// in bytes. This is used for the block size in EraseBlocks. -// It must be a power of two, and may be as small as 1. A typical size is 4096. -func (bd *BlockDevice) EraseBlockSize() int64 { - return bd.eraseBlockSize -} - // blkIdxer is a helper for calculating block indices and offsets. type blkIdxer struct { blockshift int64 From 62f51445b61479f95c213825ea1f83ec2e8d7848 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 23 Jan 2024 20:38:30 -0300 Subject: [PATCH 25/25] fix examples/sd/main.go --- examples/sd/main.go | 3 +-- sd/definitions.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/sd/main.go b/examples/sd/main.go index e80a3c2e1..706f50c06 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -48,8 +48,7 @@ func main() { cid := sdcard.CID() fmt.Printf("name=%s\ncsd=\n%s\n", cid.ProductName(), csd.String()) - const placeholderEraseSectorSize = 512 - bd, err := sd.NewBlockDevice(sdcard, int(csd.ReadBlockLen()), csd.NumberOfBlocks(), placeholderEraseSectorSize) + bd, err := sd.NewBlockDevice(sdcard, csd.ReadBlockLen(), csd.NumberOfBlocks()) if err != nil { panic("block device creation:" + err.Error()) } diff --git a/sd/definitions.go b/sd/definitions.go index c5df57030..f58d3ed0c 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -154,7 +154,7 @@ func (c *CSD) CommandClasses() CommandClasses { } // ReadBlockLen returns the Max Read Data Block Length in bytes. -func (c *CSD) ReadBlockLen() int64 { return 1 << c.ReadBlockLenShift() } +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