Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ For example, when using FatFs:
| `disk_read` | `tsd_card_read` |
| `disk_write` | `tsd_card_write` |
| `CTRL_SYNC` in `disk_ioctl` | `tsd_card_sync` |
| `CTRL_TRIM` in `disk_ioctl` | `tsd_card_erase` |
| `GET_SECTOR_SIZE` in `disk_ioctl` | `TSD_CARD_BLOCK_SIZE` |
| `GET_SECTOR_COUNT` in `disk_ioctl` | `card.blocks` |

Expand Down
22 changes: 17 additions & 5 deletions include/tinysd/card.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct tsd_card {
};

/**
* Bring a card up at a bus width and clock.
* Initialize a card at a bus width and clock.
*
* @param card the card
* @param host the host the card sits on, started by the caller
Expand All @@ -44,8 +44,10 @@ struct tsd_card {
int tsd_card_init(struct tsd_card *card, struct tsd_host *host, uint8_t width, uint32_t clock_khz);

/**
* Read blocks, leaving the read open for the next call to continue on a host that offers continue_read. Call
* tsd_card_sync to end it.
* Read blocks.
*
* On a host that offers continue_read, the read stays open for the next read to continue. A write, an erase, a read
* elsewhere or tsd_card_sync ends it.
*
* @param card the card
* @param buffer receives count blocks, aligned as the host requires
Expand All @@ -56,7 +58,7 @@ int tsd_card_init(struct tsd_card *card, struct tsd_host *host, uint8_t width, u
int tsd_card_read(struct tsd_card *card, void *buffer, uint32_t block, uint32_t count);

/**
* Write blocks, all within one call, ending any open read first.
* Write blocks.
*
* @param card the card
* @param buffer count blocks to write, aligned as the host requires
Expand All @@ -67,7 +69,17 @@ int tsd_card_read(struct tsd_card *card, void *buffer, uint32_t block, uint32_t
int tsd_card_write(struct tsd_card *card, const void *buffer, uint32_t block, uint32_t count);

/**
* End any open read, leaving the card idle, such as before a power down or for FatFs's CTRL_SYNC.
* Erase blocks.
*
* @param card the card
* @param block the first block
* @param count the number of blocks
* @return 0 or a negative tsd_error
*/
int tsd_card_erase(struct tsd_card *card, uint32_t block, uint32_t count);

/**
* End any open read.
*
* @param card the card
* @return 0 or a negative tsd_error
Expand Down
41 changes: 41 additions & 0 deletions include/tinysd/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,38 @@
/** ACMD6 argument for a 4-bit bus. */
#define TSD_ACMD6_BUS_WIDTH_4BIT 0x2u

/**
* Current state values in the card status, from Table 4-42 of the SD specification.
*/
enum tsd_status_state {
/** Idle State. */
TSD_STATUS_STATE_IDLE = 0,

/** Ready State. */
TSD_STATUS_STATE_READY = 1,

/** Identification State. */
TSD_STATUS_STATE_IDENT = 2,

/** Stand-by State. */
TSD_STATUS_STATE_STBY = 3,

/** Transfer State. */
TSD_STATUS_STATE_TRAN = 4,

/** Sending-data State. */
TSD_STATUS_STATE_DATA = 5,

/** Receive-data State. */
TSD_STATUS_STATE_RCV = 6,

/** Programming State. */
TSD_STATUS_STATE_PRG = 7,

/** Disconnect State. */
TSD_STATUS_STATE_DIS = 8,
};

/**
* Command indexes, named as in the SD specification.
*/
Expand Down Expand Up @@ -118,6 +150,15 @@ enum tsd_command_index {
/** Write blocks until CMD12. */
TSD_CMD_WRITE_MULTIPLE_BLOCK = 25,

/** Set the first block to erase. */
TSD_CMD_ERASE_WR_BLK_START = 32,

/** Set the last block to erase. */
TSD_CMD_ERASE_WR_BLK_END = 33,

/** Erase the blocks CMD32 and CMD33 set. */
TSD_CMD_ERASE = 38,

/** Read or write one register byte of an SDIO card. */
TSD_CMD_IO_RW_DIRECT = 52,

Expand Down
2 changes: 1 addition & 1 deletion include/tinysd/sdio.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ struct tsd_sdio {
int tsd_sdio_identify(struct tsd_sdio *card, struct tsd_host *host);

/**
* Bring a card up at a bus width and clock.
* Initialize a card at a bus width and clock.
*
* @param card the card
* @param host the host the card sits on, started by the caller
Expand Down
2 changes: 1 addition & 1 deletion src/card_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ int tsd_card_init(struct tsd_card *card, struct tsd_host *host, uint8_t width, u
if (ret < 0)
return ret;

// Select the card by its address, taking an R1 since it has nothing to be busy with, like Linux and Zephyr
// Select the card by its address, taking an R1 since it has nothing to be busy with
ret = tsd_host_send_command(card->host, TSD_CMD_SELECT_CARD, card->address << TSD_RCA_SHIFT, TSD_RESPONSE_R1, NULL);
if (ret < 0)
return ret;
Expand Down
103 changes: 87 additions & 16 deletions src/card_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,37 @@
#include <tinysd/bits.h>
#include <tinysd/commands.h>
#include <tinysd/config.h>
#include <tinysd/delay.h>

#include <tinysd/card.h>

// next_block when no read has come before
#define NO_BLOCK UINT32_MAX

// Status errors that mark a failed write, leaving out the range error an open write may raise
static const uint32_t write_errors = TSD_STATUS_ADDRESS_ERROR | TSD_STATUS_BLOCK_LEN_ERROR | TSD_STATUS_WP_VIOLATION |
TSD_STATUS_CARD_ECC_FAILED | TSD_STATUS_CC_ERROR | TSD_STATUS_ERROR;
// Longest wait for the card to finish a write, in milliseconds
#define WRITE_TIMEOUT_MS 10000

// Time the card may take to erase each block, in milliseconds, from the SD specification
#define ERASE_TIMEOUT_PER_BLOCK_MS 250

// Delay between CMD13 tries while the card is busy, in microseconds
#define READY_DELAY_US 125

// Status errors that mark a failed write
#define WRITE_ERRORS \
(TSD_STATUS_ADDRESS_ERROR | \
TSD_STATUS_BLOCK_LEN_ERROR | \
TSD_STATUS_WP_VIOLATION | \
TSD_STATUS_CARD_ECC_FAILED | \
TSD_STATUS_CC_ERROR | \
TSD_STATUS_ERROR)

// Status errors that mark a failed erase
#define ERASE_ERRORS \
(WRITE_ERRORS | \
TSD_STATUS_ERASE_SEQ_ERROR | \
TSD_STATUS_ERASE_PARAM | \
TSD_STATUS_WP_ERASE_SKIP)

// Address a block by number on a high capacity card and by byte offset on the others
static uint32_t block_address(struct tsd_card *card, uint32_t block)
Expand Down Expand Up @@ -61,25 +83,38 @@ static int transfer_blocks(struct tsd_card *card, void *buffer, uint32_t block,
ret = stop_ret;

// Report an error the card flagged while it took the blocks of a write
if (ret == 0 && write && (status & write_errors))
if (ret == 0 && write && (status & WRITE_ERRORS))
ret = TSD_ERR_CARD;
}

return ret;
}

// Ask the card for its status with CMD13 and report an error it flags
static int check_status(struct tsd_card *card)
// Poll the card status until it is ready for data
static int wait_ready(struct tsd_card *card, uint32_t errors, uint64_t timeout_us)
{
// Ask the card for its status
uint32_t address = card->address << TSD_RCA_SHIFT;
uint32_t status = 0;
int ret = tsd_host_send_command(card->host, TSD_CMD_SEND_STATUS, address, TSD_RESPONSE_R1, &status);
if (ret < 0)
return ret;
for (uint64_t waited_us = 0; waited_us < timeout_us; waited_us += READY_DELAY_US) {
// Ask the card for its status
uint32_t status = 0;
int ret = tsd_host_send_command(card->host, TSD_CMD_SEND_STATUS, address, TSD_RESPONSE_R1, &status);
if (ret < 0)
return ret;

// Report an error bit the card set
return status & write_errors ? TSD_ERR_CARD : 0;
// Report an error bit the card set
if (status & errors)
return TSD_ERR_CARD;

// Finish once the card is ready for data in the transfer state
uint32_t state = TSD_FIELD_GET(TSD_STATUS_CURRENT_STATE, status);
if ((status & TSD_STATUS_READY_FOR_DATA) && state == TSD_STATUS_STATE_TRAN)
return 0;

// Wait before asking again
tsd_delay_us(READY_DELAY_US);
}

return TSD_ERR_TIMEOUT;
}

// End an open read with CMD12, which has no busy after a read, leaving the card ready for another command
Expand Down Expand Up @@ -167,16 +202,52 @@ int tsd_card_write(struct tsd_card *card, const void *buffer, uint32_t block, ui
// Forget where a read would follow on, since the write comes between
card->next_block = NO_BLOCK;

// End an open read, since the card takes no other command while it sends
// End an open read
int ret = stop_read(card);
if (ret < 0)
return ret;

// Write the blocks, then check the card's status for an error from programming them
// Write the blocks, then wait for the card to finish programming them
ret = transfer_blocks(card, (void *)buffer, block, count, true);
if (ret < 0)
return ret;
return check_status(card);

return wait_ready(card, WRITE_ERRORS, WRITE_TIMEOUT_MS * 1000);
}

int tsd_card_erase(struct tsd_card *card, uint32_t block, uint32_t count)
{
// Refuse an empty erase or one past the end of the card
if (count == 0 || block >= card->blocks || count > card->blocks - block)
return TSD_ERR_PARAM;

// Forget where a read would follow on, since the erase comes between
card->next_block = NO_BLOCK;

// End an open read
int ret = stop_read(card);
if (ret < 0)
return ret;

// Set the first block
uint32_t first = block_address(card, block);
ret = tsd_host_send_command(card->host, TSD_CMD_ERASE_WR_BLK_START, first, TSD_RESPONSE_R1, NULL);
if (ret < 0)
return ret;

// Set the last block
uint32_t last = block_address(card, block + count - 1);
ret = tsd_host_send_command(card->host, TSD_CMD_ERASE_WR_BLK_END, last, TSD_RESPONSE_R1, NULL);
if (ret < 0)
return ret;

// Erase them, taking an R1 since a long erase can outlast the host's busy wait
ret = tsd_host_send_command(card->host, TSD_CMD_ERASE, 0, TSD_RESPONSE_R1, NULL);
if (ret < 0)
return ret;

// Wait for the card to finish
return wait_ready(card, ERASE_ERRORS, (uint64_t)count * ERASE_TIMEOUT_PER_BLOCK_MS * 1000);
}

int tsd_card_sync(struct tsd_card *card)
Expand Down
4 changes: 2 additions & 2 deletions src/sdhci.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
// SDMA buffer boundary, which a buffer must not cross
#define DMA_BOUNDARY (4096u << SDMA_BOUNDARY_512K)

// Delay between status reads while a wait polls, in microseconds, like Linux SDHCI
// Delay between status reads while a wait polls, in microseconds
#define WAIT_STEP_US 10

// Longest wait for a command phase, in milliseconds
Expand Down Expand Up @@ -610,7 +610,7 @@ static int run_command(struct tsd_sdhci *sdhci, struct tsd_command *command, str
// Wait for the previous command to leave the command line
int ret = wait_register(sdhci, REG_PRESENT_STATE, STATE_CMD_INHIBIT, false, COMMAND_TIMEOUT_MS, NULL);

// Wait for the data lines too, which stay busy while the card programs a write, like Zephyr's TI driver
// Wait for the data lines too, which stay busy while the card programs a write
if (ret == 0)
ret = wait_register(sdhci, REG_PRESENT_STATE, STATE_DAT_INHIBIT, false, DATA_TIMEOUT_MS, NULL);
if (ret < 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/sdio_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// Highest function number the argument's 3-bit field holds
#define FUNCTION_MAX 7

// Turn the error flags of an R5 into a tsd_error, like Linux
// Turn the error flags of an R5 into a tsd_error
static int check_response(uint32_t response)
{
// Report a general error first, then a bad function or argument
Expand Down
29 changes: 28 additions & 1 deletion test/test_card.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ void setUp(void)
host.replies[TSD_CMD_SEND_IF_COND].response[0] = 0x1aa;
host.replies[TSD_ACMD_SD_SEND_OP_COND].response[0] = TSD_OCR_READY | TSD_OCR_CCS;
host.replies[TSD_CMD_SEND_RELATIVE_ADDR].response[0] = 0x00010000;

// Script a card ready for data in the transfer state, for the CMD13 after a write or an erase
host.replies[TSD_CMD_SEND_STATUS].response[0] =
TSD_STATUS_READY_FOR_DATA | TSD_FIELD_PREP(TSD_STATUS_CURRENT_STATE, TSD_STATUS_STATE_TRAN);
}

void tearDown(void)
Expand Down Expand Up @@ -144,14 +148,35 @@ static void test_multiple_block_write(void)
TEST_ASSERT_EQUAL_INT(TSD_ERR_CARD, tsd_card_write(&card, buffer, 0, 4));
}

// Test that a read or write past the end is refused without a command
// Test that an erase sets its first and last blocks, then erases without a busy
static void test_erase(void)
{
init_high_capacity();

TEST_ASSERT_EQUAL_INT(0, tsd_card_erase(&card, 8, 8));
TEST_ASSERT_EQUAL_UINT32(8, harness_find(TSD_CMD_ERASE_WR_BLK_START)->argument);
TEST_ASSERT_EQUAL_UINT32(15, harness_find(TSD_CMD_ERASE_WR_BLK_END)->argument);
TEST_ASSERT_EQUAL_INT(TSD_RESPONSE_R1, harness_find(TSD_CMD_ERASE)->response_type);
}

// Test that an erase reports an error bit the card sets once it finishes
static void test_erase_error(void)
{
init_high_capacity();
host.replies[TSD_CMD_SEND_STATUS].response[0] = TSD_STATUS_ERASE_PARAM;

TEST_ASSERT_EQUAL_INT(TSD_ERR_CARD, tsd_card_erase(&card, 8, 8));
}

// Test that a read, write or erase past the end is refused without a command
static void test_past_the_end(void)
{
init_high_capacity();
int sent = host.command_count;

TEST_ASSERT_EQUAL_INT(TSD_ERR_PARAM, tsd_card_read(&card, buffer, card.blocks - 1, 2));
TEST_ASSERT_EQUAL_INT(TSD_ERR_PARAM, tsd_card_write(&card, buffer, card.blocks, 1));
TEST_ASSERT_EQUAL_INT(TSD_ERR_PARAM, tsd_card_erase(&card, card.blocks - 1, 2));
TEST_ASSERT_EQUAL_INT(sent, host.command_count);
}

Expand All @@ -168,6 +193,8 @@ int main(int argc, char **argv)
RUN_TEST(test_read_high_capacity_by_block);
RUN_TEST(test_open_read);
RUN_TEST(test_multiple_block_write);
RUN_TEST(test_erase);
RUN_TEST(test_erase_error);
RUN_TEST(test_past_the_end);

return UNITY_END();
Expand Down
Loading