1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! 块写入路径:CMD24 单块、CMD25 多块(PIO),以及 CMD25 + ADMA2(DMA)。
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
use super::consts::*;
use super::{
ADMA2_ATTR_ACT_TRAN, ADMA2_ATTR_END, ADMA2_ATTR_VALID, ADMA2_MAX_PER_DESC, Adma2Desc, Sdmmc,
};
use crate::utils::cache::dcache_clean_range;
use crate::utils::delay;
impl Sdmmc {
/// 向 SD 卡写入一个数据块 (512 字节)
///
/// # 参数
/// - `block_id`: 要写入的块号 (逻辑块地址 LBA)
/// - `data`: 要写入的数据,必须为 512 字节
///
/// # 返回值
/// - `Ok(())`: 写入成功
/// - `Err(CmdError)`: 写入失败,返回错误类型
pub fn write_block(&self, block_id: u32, data: &[u8]) -> Result<(), CmdError> {
// 发送 CMD24 写入单块命令
self.cmd_transfer(CommandType::CMD(24), block_id, 1, false)?;
// 将数据写入数据缓冲区
self.write_buff(data)?;
// 等待数据传输完成
let res = self.wait_for_xfer_done();
// 清除中断状态寄存器
self.regs
.norm_and_err_int_sts
.set(self.regs.norm_and_err_int_sts.get());
res
}
/// 使用 CMD24 写入**单个**数据块 (512 字节)。
///
/// 语义与 [`Sdmmc::write_block`] 相同,命名与 [`Sdmmc::read_block_single`] 对齐。
pub fn write_block_single(&self, block_id: u32, data: &[u8]) -> Result<(), CmdError> {
assert_eq!(data.len(), BLOCK_SIZE);
self.write_block(block_id, data)
}
/// 使用 CMD25 (WRITE_MULTIPLE_BLOCK) **一次性**写入多个连续数据块 (PIO)。
///
/// 单条命令即可完成 `data.len() / BLOCK_SIZE` 个块的写入,控制器在传输
/// 结束后通过 AUTO_CMD12 自动发送停止命令。CPU 逐字把数据写入数据端口。
///
/// # 参数
/// - `start_block`: 起始块号 (LBA)
/// - `data`: 源缓冲区,长度须为 [`BLOCK_SIZE`] 的整数倍且非空
pub fn write_blocks(&self, start_block: u32, data: &[u8]) -> Result<(), CmdError> {
assert!(!data.is_empty(), "write_blocks: buffer must not be empty");
assert!(
data.len().is_multiple_of(BLOCK_SIZE),
"write_blocks: buffer length must be a multiple of BLOCK_SIZE"
);
let blk_cnt = data.len() / BLOCK_SIZE;
for attempt in 0..XFER_RETRY {
if attempt > 0 {
self.reset_dat_cmd_lines();
log::warn!(
"sdmmc write retry {}/{}: block {}, {} blocks",
attempt,
XFER_RETRY,
start_block,
blk_cnt
);
}
// 发送 CMD25 多块写入命令
let r = self
.cmd_transfer(CommandType::CMD(25), start_block, blk_cnt as _, false)
.and_then(|_| self.write_buff(data))
.and_then(|_| self.wait_for_xfer_done());
// 清除中断状态寄存器
self.regs
.norm_and_err_int_sts
.set(self.regs.norm_and_err_int_sts.get());
match r {
Ok(()) => return Ok(()),
Err(CmdError::IntError) => continue,
Err(e) => return Err(e),
}
}
Err(CmdError::IntError)
}
/// 使用 CMD25 + **ADMA2 (32 位)** 一次性写入多个连续数据块 (DMA)。
///
/// 数据由 DMA 引擎直接从内存读出送往 SD 卡,CPU 不再逐字搬运。
///
/// 职责划分与 [`Sdmmc::read_blocks_adma2`] 相同:调用方提供数据缓冲/描述符表
/// 及其物理地址;cache 一致性由本函数处理(写方向仅需传输前 clean)。
///
/// # Safety
/// 物理地址须与切片指向同一段有效 DMA 内存。
pub unsafe fn write_blocks_adma2(
&self,
start_block: u32,
data: &[u8],
data_paddr: u64,
desc_table: &mut [Adma2Desc],
desc_paddr: u64,
) -> Result<(), CmdError> {
assert!(
!data.is_empty(),
"write_blocks_adma2: buffer must not be empty"
);
assert!(
data.len().is_multiple_of(BLOCK_SIZE),
"write_blocks_adma2: buffer length must be a multiple of BLOCK_SIZE"
);
let total_len = data.len();
let blk_cnt = total_len / BLOCK_SIZE;
// 构建 ADMA2 描述符表。
let desc_needed = total_len.div_ceil(ADMA2_MAX_PER_DESC);
assert!(
desc_table.len() >= desc_needed,
"write_blocks_adma2: descriptor table too small"
);
let mut offset = 0usize;
for (i, desc) in desc_table.iter_mut().enumerate().take(desc_needed) {
let remain = total_len - offset;
let chunk = core::cmp::min(remain, ADMA2_MAX_PER_DESC);
let mut attr = ADMA2_ATTR_VALID | ADMA2_ATTR_ACT_TRAN;
if i == desc_needed - 1 {
attr |= ADMA2_ATTR_END;
}
*desc = Adma2Desc {
attr,
len: chunk as u16,
addr: (data_paddr + offset as u64) as u32,
};
offset += chunk;
}
// 写方向:把描述符表和源数据都 clean 到内存,保证 DMA 读到最新数据。
let desc_bytes = desc_needed * core::mem::size_of::<Adma2Desc>();
dcache_clean_range(desc_table.as_ptr() as usize, desc_bytes);
dcache_clean_range(data.as_ptr() as usize, total_len);
// 选择 ADMA2 并写入描述符表物理基址。
self.regs
.host_ctl1_pwr_bg_wup
.modify(HOST_CTL1_PWR_BG_WUP::DMA_SEL::ADMA2);
self.regs
.adma_sys_addr_low
.set((desc_paddr & 0xFFFF_FFFF) as u32);
self.regs.adma_sys_addr_high.set(0);
// 发送 CMD25(dma=true 置 DMA_EN),DMA 引擎负责后续数据搬运。
self.cmd_transfer(CommandType::CMD(25), start_block, blk_cnt as _, true)?;
// 等待传输完成(写方向无需 invalidate)。
let res = self.wait_for_xfer_done();
self.regs
.norm_and_err_int_sts
.set(self.regs.norm_and_err_int_sts.get());
res
}
/// 向数据缓冲区写入数据(PIO)。
///
/// 支持一个或多个块:`data` 长度须为 [`BLOCK_SIZE`] 的整数倍,
/// 按块循环等待 `BUF_WRDY` 后逐 32 位写入数据端口。
///
/// # 参数
/// - `data`: 要写入的数据,长度须为 [`BLOCK_SIZE`] 的整数倍
fn write_buff(&self, data: &[u8]) -> Result<(), CmdError> {
assert!(data.len().is_multiple_of(BLOCK_SIZE));
for block_buf in data.chunks_exact(BLOCK_SIZE) {
// 等待缓冲区写就绪
loop {
let sts = self.regs.norm_and_err_int_sts.extract();
if sts.is_set(NORM_AND_ERR_INT_STS::BUF_WRDY) {
// 清除缓冲区写就绪标志
self.regs
.norm_and_err_int_sts
.write(NORM_AND_ERR_INT_STS::BUF_WRDY::SET);
break;
}
if sts.is_set(NORM_AND_ERR_INT_STS::ERR_INT) {
// 清除错误中断标志
self.regs
.norm_and_err_int_sts
.write(NORM_AND_ERR_INT_STS::ERR_INT::SET);
return Err(CmdError::IntError);
}
delay(1);
}
// 向数据端口寄存器写入数据
// 每次写入 32 位 (4 字节),共写入 128 次
for chunk in block_buf.chunks_exact(4) {
let value = u32::from_le_bytes(chunk.try_into().unwrap());
self.regs.buf_data_port.set(value);
delay(1);
}
}
Ok(())
}
}