1 | v1: | 1 | v1: |
---|---|---|---|
2 | 1. Fix boundary_count overflow | 2 | 1. Fix boundary_count overflow |
3 | 2. Fix data transfer did not complete if data size is bigger then SDMA Buffer Boundary | 3 | 2. Fix data transfer did not complete if data size is bigger then SDMA Buffer Boundary |
4 | 4 | ||
5 | v2: | ||
6 | 1. fix typo | ||
7 | 2. update to none RFC patch | ||
8 | 3. check the most upper byte of SDMA System Address Register (0x00) is written, | ||
9 | then restarts SDMA data transfer. | ||
10 | |||
5 | Jamin Lin (2): | 11 | Jamin Lin (2): |
6 | RFC:sd:sdhci: Fix boundary_count overflow in | 12 | hw/sd/sdhci: Fix boundary_count overflow in |
7 | sdhci_sdma_transfer_multi_blocks | 13 | sdhci_sdma_transfer_multi_blocks |
8 | RFC:sd:sdhci: Fix data transfer did not complete if data size is | 14 | hw/sd/sdhci: Fix data transfer did not complete if data size is bigger |
9 | bigger then SDMA Buffer Boundary | 15 | than SDMA Buffer Boundary |
10 | 16 | ||
11 | hw/sd/sdhci.c | 27 +++++++++++++-------------- | 17 | hw/sd/sdhci.c | 20 ++++++++++++++++---- |
12 | 1 file changed, 13 insertions(+), 14 deletions(-) | 18 | 1 file changed, 16 insertions(+), 4 deletions(-) |
13 | 19 | ||
14 | -- | 20 | -- |
15 | 2.34.1 | 21 | 2.34.1 | diff view generated by jsdifflib |
1 | How to reproduce it: | 1 | How to reproduce it: |
---|---|---|---|
2 | 1. The value of "s->blksie" was 0x7200. The bits[14:12] was "111", so the buffer | 2 | 1. The value of "s->blksie" was 0x7200. The bits[14:12] was "111", so the buffer |
3 | boundary was 0x80000.(512Kbytes). This SDMA buffer boundary the same as | 3 | boundary was 0x80000.(512Kbytes). This SDMA buffer boundary was the same as |
4 | u-boot default value. | 4 | u-boot default value. |
5 | The bit[11:0] is "001000000000", so the block size is 0x200.(512bytes) | 5 | The bit[11:0] was "001000000000", so the block size was 0x200.(512bytes) |
6 | 2. The SDMA address was 0x83123456 which was not page aligned and | 6 | 2. The SDMA address was 0x83123456 which was not page aligned and |
7 | "s->sdmasysad % boundary_chk" was 0x23456. The value of boundary_count was | 7 | "s->sdmasysad % boundary_chk" was 0x23456. The value of boundary_count was |
8 | 0x5cbaa.("boundary_chk - (s->sdmasysad % boundary_chk)" --> | 8 | 0x5cbaa.("boundary_chk - (s->sdmasysad % boundary_chk)" --> |
9 | "(0x80000 - 0x23456)") | 9 | "(0x80000 - 0x23456)") |
10 | 10 | ||
11 | However, boundary_count did not aligned the block size 512 bytes and the SDMA | 11 | However, boundary_count did not align the block size 512 bytes and the SDMA |
12 | address is not page aligned(0x80000), so the following if-statement never be true, | 12 | address was not page aligned(0x80000), so the following if-statement never be true, |
13 | ``` | 13 | ``` |
14 | if (((boundary_count + begin) < block_size) && page_aligned) | 14 | if (((boundary_count + begin) < block_size) && page_aligned) |
15 | ```` | 15 | ```` |
16 | 16 | ||
17 | Finally, it caused boundary_count overflow because its data type was uint32_t. | 17 | Finally, it caused boundary_count overflow because its data type was uint32_t. |
18 | Ex: the last boundary_count was 0x1aa and "0x1aa - 0x200" became "0xffffffaa". | 18 | Ex: the last boundary_count was 0x1aa and "0x1aa - 0x200" became "0xffffffaa". |
19 | It is the wrong behavior. | 19 | It is the wrong behavior. |
20 | 20 | ||
21 | To fix it, it seems we can directly check the "boundary_count < blocksize" | 21 | To fix it, change to check boundary_count smaller than block size if system |
22 | instead of "boundary_count < blocksize && page_aligned" | 22 | address did not page align |
23 | 23 | ||
24 | Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> | 24 | Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> |
25 | --- | 25 | --- |
26 | hw/sd/sdhci.c | 18 ++++-------------- | 26 | hw/sd/sdhci.c | 8 ++++---- |
27 | 1 file changed, 4 insertions(+), 14 deletions(-) | 27 | 1 file changed, 4 insertions(+), 4 deletions(-) |
28 | 28 | ||
29 | diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c | 29 | diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c |
30 | index XXXXXXX..XXXXXXX 100644 | 30 | index XXXXXXX..XXXXXXX 100644 |
31 | --- a/hw/sd/sdhci.c | 31 | --- a/hw/sd/sdhci.c |
32 | +++ b/hw/sd/sdhci.c | 32 | +++ b/hw/sd/sdhci.c |
33 | @@ -XXX,XX +XXX,XX @@ static void sdhci_write_dataport(SDHCIState *s, uint32_t value, unsigned size) | ||
34 | /* Multi block SDMA transfer */ | ||
35 | static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) | ||
36 | { | ||
37 | - bool page_aligned = false; | ||
38 | unsigned int begin; | ||
39 | const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK; | ||
40 | uint32_t boundary_chk = 1 << (((s->blksize & ~BLOCK_SIZE_MASK) >> 12) + 12); | ||
41 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) | ||
42 | return; | ||
43 | } | ||
44 | |||
45 | - /* | ||
46 | - * XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for | ||
47 | - * possible stop at page boundary if initial address is not page aligned, | ||
48 | - * allow them to work properly | ||
49 | - */ | ||
50 | - if ((s->sdmasysad % boundary_chk) == 0) { | ||
51 | - page_aligned = true; | ||
52 | - } | ||
53 | - | ||
54 | s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE; | ||
55 | if (s->trnmod & SDHC_TRNS_READ) { | ||
56 | s->prnsts |= SDHC_DOING_READ; | ||
57 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) | 33 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) |
58 | sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size); | 34 | sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size); |
59 | } | 35 | } |
60 | begin = s->data_count; | 36 | begin = s->data_count; |
61 | - if (((boundary_count + begin) < block_size) && page_aligned) { | 37 | - if (((boundary_count + begin) < block_size) && page_aligned) { |
62 | + if (((boundary_count + begin) < block_size)) { | 38 | + if (((boundary_count + begin) < block_size) && !page_aligned) { |
63 | s->data_count = boundary_count + begin; | 39 | s->data_count = boundary_count + begin; |
64 | boundary_count = 0; | 40 | boundary_count = 0; |
65 | } else { | 41 | } else { |
66 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) | 42 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) |
67 | if (s->data_count == block_size) { | 43 | if (s->data_count == block_size) { |
... | ... | ||
75 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) | 51 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) |
76 | s->prnsts |= SDHC_DOING_WRITE; | 52 | s->prnsts |= SDHC_DOING_WRITE; |
77 | while (s->blkcnt) { | 53 | while (s->blkcnt) { |
78 | begin = s->data_count; | 54 | begin = s->data_count; |
79 | - if (((boundary_count + begin) < block_size) && page_aligned) { | 55 | - if (((boundary_count + begin) < block_size) && page_aligned) { |
80 | + if (((boundary_count + begin) < block_size)) { | 56 | + if (((boundary_count + begin) < block_size) && !page_aligned) { |
81 | s->data_count = boundary_count + begin; | 57 | s->data_count = boundary_count + begin; |
82 | boundary_count = 0; | 58 | boundary_count = 0; |
83 | } else { | 59 | } else { |
84 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) | 60 | @@ -XXX,XX +XXX,XX @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) |
85 | s->blkcnt--; | 61 | s->blkcnt--; |
... | ... | diff view generated by jsdifflib |
1 | According to the design of sdhci_sdma_transfer_multi_blocks, if the | 1 | According to the design of sdhci_sdma_transfer_multi_blocks, if the |
---|---|---|---|
2 | "s->blkcnt * 512" was bigger than the SDMA Buffer boundary, it breaked the | 2 | "s->blkcnt * 512" was bigger than the SDMA Buffer boundary, it break the |
3 | while loop of data transfer and set SDHC_NISEN_DMA in the normal interreupt | 3 | while loop of data transfer and set SDHC_NISEN_DMA in the normal interrupt |
4 | status to notify the firmware that this SDMA boundary buffer Transfer Complete | 4 | status to notify the firmware that this SDMA boundary buffer Transfer Complete |
5 | and firmware should set the system address of the next SDMA boundary buffer | 5 | and firmware should set the system address of the next SDMA boundary buffer |
6 | for the remaining data transfer. | 6 | for the remained data transfer. |
7 | 7 | ||
8 | However, after firmware set the system address of the next SDMA boundary buffer | 8 | However, after firmware set the system address of the next SDMA boundary buffer |
9 | in the SDMA System Address Register(0x00), SDHCI modle did not start the data | 9 | in the SDMA System Address Register(0x00), SDHCI model did not restart the data |
10 | transfer, again. Finally, firmware breaked the data transfer because firmware | 10 | transfer, again. Finally, firmware break the data transfer because firmware |
11 | did not receive the DMA Interrupt and Tansfer Complete Interrupt from SDHCI | 11 | did not receive the either "DMA Interrupt" or "Transfer Complete Interrupt" |
12 | model. | 12 | from SDHCI model. |
13 | 13 | ||
14 | Error log from u-boot | 14 | Error log from u-boot |
15 | ``` | 15 | ``` |
16 | sdhci_transfer_data: Transfer data timeout | 16 | sdhci_transfer_data: Transfer data timeout |
17 | ** fs_devread read error - block | 17 | ** fs_devread read error - block |
18 | ``` | 18 | ``` |
19 | 19 | ||
20 | According to the following mention from SDMA System Address Refister of SDHCI | 20 | According to the following mention from SDMA System Address Register of SDHCI |
21 | spec, | 21 | spec, |
22 | ''' | 22 | ''' |
23 | This register contains the system memory address for an SDMA transfer in | 23 | This register contains the system memory address for an SDMA transfer in |
24 | 32-bit addressing mode. When the Host Controller stops an SDMA transfer, | 24 | 32-bit addressing mode. When the Host Controller stops an SDMA transfer, |
25 | this register shall point to the system address of the next contiguous data | 25 | this register shall point to the system address of the next contiguous data |
... | ... | ||
37 | Driver sets the next system address of the next data position to this register. | 37 | Driver sets the next system address of the next data position to this register. |
38 | When the most upper byte of this register (003h) is written, the Host Controller | 38 | When the most upper byte of this register (003h) is written, the Host Controller |
39 | restarts the SDMA transfer. | 39 | restarts the SDMA transfer. |
40 | ''', | 40 | ''', |
41 | 41 | ||
42 | restrat the data transfer if firmware set the SDMA System Address, s->blkcnt | 42 | restart the data transfer if firmware writes the most upper byte of SDMA System |
43 | is bigger than 0 and SDHCI is in the data transfer state. | 43 | Address, s->blkcnt is bigger than 0 and SDHCI is in the data transfer state. |
44 | 44 | ||
45 | Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> | 45 | Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> |
46 | --- | 46 | --- |
47 | hw/sd/sdhci.c | 9 +++++++++ | 47 | hw/sd/sdhci.c | 12 ++++++++++++ |
48 | 1 file changed, 9 insertions(+) | 48 | 1 file changed, 12 insertions(+) |
49 | 49 | ||
50 | diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c | 50 | diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c |
51 | index XXXXXXX..XXXXXXX 100644 | 51 | index XXXXXXX..XXXXXXX 100644 |
52 | --- a/hw/sd/sdhci.c | 52 | --- a/hw/sd/sdhci.c |
53 | +++ b/hw/sd/sdhci.c | 53 | +++ b/hw/sd/sdhci.c |
54 | @@ -XXX,XX +XXX,XX @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) | 54 | @@ -XXX,XX +XXX,XX @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) |
55 | sdhci_sdma_transfer_single_block(s); | 55 | sdhci_sdma_transfer_single_block(s); |
56 | } | 56 | } |
57 | } | 57 | } |
58 | + } else if (TRANSFERRING_DATA(s->prnsts)) { | 58 | + } else if (TRANSFERRING_DATA(s->prnsts)) { |
59 | + /* restarts the SDMA transfer if blkcnt is not zero */ | 59 | + s->sdmasysad = (s->sdmasysad & mask) | value; |
60 | + if (s->blkcnt && SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) { | 60 | + MASKED_WRITE(s->sdmasysad, mask, value); |
61 | + /* restarts the SDMA transfer if the most upper byte is written */ | ||
62 | + if ((s->sdmasysad & 0xFF000000) && s->blkcnt && | ||
63 | + SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) { | ||
61 | + if (s->trnmod & SDHC_TRNS_MULTI) { | 64 | + if (s->trnmod & SDHC_TRNS_MULTI) { |
62 | + sdhci_sdma_transfer_multi_blocks(s); | 65 | + sdhci_sdma_transfer_multi_blocks(s); |
63 | + } else { | 66 | + } else { |
64 | + sdhci_sdma_transfer_single_block(s); | 67 | + sdhci_sdma_transfer_single_block(s); |
65 | + } | 68 | + } |
66 | + } | 69 | + } |
67 | } | 70 | } |
68 | break; | 71 | break; |
69 | case SDHC_BLKSIZE: | 72 | case SDHC_BLKSIZE: |
70 | -- | 73 | -- |
71 | 2.34.1 | 74 | 2.34.1 | diff view generated by jsdifflib |