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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
//! 块读取路径:CMD17 单块、CMD18 多块(PIO),以及 CMD18 + 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, dcache_invalidate_range};
use crate::utils::delay;
impl Sdmmc {
/// 使用 CMD17 从 SD 卡读取**单个**数据块 (512 字节)。
///
/// 每次调用发送一条 CMD17 命令,仅传输一个块。适用于需要精确
/// 控制单块访问的场景;批量读取请使用 [`Sdmmc::read_blocks`]。
///
/// # 参数
/// - `block_id`: 要读取的块号 (逻辑块地址 LBA)
/// - `buffer`: 用于存储读取数据的缓冲区,必须为 [`BLOCK_SIZE`] (512) 字节
pub fn read_block_single(&self, block_id: u32, buffer: &mut [u8]) -> Result<(), CmdError> {
assert_eq!(buffer.len(), BLOCK_SIZE);
// 发送 CMD17 读取单块命令
self.cmd_transfer(CommandType::CMD(17), block_id, 1, false)?;
// 从数据缓冲区读取数据
self.read_buff(buffer)?;
// 等待数据传输完成
let res = self.wait_for_xfer_done();
// 清除中断状态寄存器
self.regs
.norm_and_err_int_sts
.set(self.regs.norm_and_err_int_sts.get());
res
}
/// 使用 CMD18 (READ_MULTIPLE_BLOCK) **一次性**从 SD 卡读取多个连续数据块。
///
/// 单条命令即可完成 `data.len() / BLOCK_SIZE` 个块的传输,控制器在
/// 传输结束后通过 AUTO_CMD12 自动发送停止命令 (CMD12)。相比循环调用
/// [`Sdmmc::read_block_single`],可显著降低每块的命令开销,提升吞吐。
///
/// # 参数
/// - `start_block`: 起始块号 (逻辑块地址 LBA)
/// - `data`: 目标缓冲区,长度必须为 [`BLOCK_SIZE`] (512) 的整数倍,
/// 且至少为一个块
///
/// # Panics
/// 当 `data` 长度不是 [`BLOCK_SIZE`] 的整数倍或为 0 时 panic。
pub fn read_blocks(&self, start_block: u32, data: &mut [u8]) -> Result<(), CmdError> {
assert!(!data.is_empty(), "read_blocks: buffer must not be empty");
assert!(
data.len().is_multiple_of(BLOCK_SIZE),
"read_blocks: buffer length must be a multiple of BLOCK_SIZE"
);
let blk_cnt = data.len() / BLOCK_SIZE;
log::debug!(
"reading {} blocks (CMD18) from block {}",
blk_cnt,
start_block
);
// 对 SDHCI 错误中断(瞬时 data CRC / 命令线错误)重试:先软复位数据/命令线,
// 再重新下发 CMD18。Linux 靠重试+DMA 容忍此类错误,此处对齐。
for attempt in 0..XFER_RETRY {
if attempt > 0 {
self.reset_dat_cmd_lines();
log::warn!(
"sdmmc read retry {}/{}: block {}, {} blocks",
attempt,
XFER_RETRY,
start_block,
blk_cnt
);
}
// 发送 CMD18 多块读取命令 (块大小/块数量在 cmd_transfer 内部配置)
let r = self
.cmd_transfer(CommandType::CMD(18), start_block, blk_cnt as _, false)
.and_then(|_| self.read_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)
}
/// 从 SD 卡读取一个或多个数据块。
///
/// 这是 [`Sdmmc::read_blocks`] 的兼容别名:`data` 长度为
/// [`BLOCK_SIZE`] 时读取单块,为其整数倍时一次性读取多块。
///
/// # 参数
/// - `block_id`: 起始块号 (逻辑块地址 LBA)
/// - `data`: 用于存储读取数据的缓冲区,长度须为 [`BLOCK_SIZE`] 的整数倍
pub fn read_block(&self, block_id: u32, data: &mut [u8]) -> Result<(), CmdError> {
self.read_blocks(block_id, data)
}
/// 使用 **ADMA2 (32 位)** 一次性读取多个连续数据块,数据由控制器 DMA
/// 引擎直接写入内存,CPU 不再逐字从数据端口搬运。
///
/// 相比 PIO 版本 [`Sdmmc::read_blocks`],DMA 消除了 `buf_data_port`
/// 轮询开销,通常可获得更高吞吐、更低 CPU 占用。
///
/// # 职责划分
/// 本 BSP 与具体 MMU / 分配器无关,因此**由调用方**负责:
/// - 提供物理连续、且已知物理地址的数据缓冲区 `data` / `data_paddr`;
/// - 提供物理连续、8 字节对齐的描述符表 `desc_table` / `desc_paddr`,
/// 容量需 `>= ceil(data.len() / ADMA2_MAX_PER_DESC)`;
/// - 保证两块内存均在 DMA 可见地址空间内。
///
/// 缓存一致性(clean/invalidate)由本函数在内部按 C906/AArch64 规则处理。
///
/// # 参数
/// - `start_block`: 起始块号 (LBA)
/// - `data`: 目标缓冲区(长度须为 [`BLOCK_SIZE`] 整数倍且非空)
/// - `data_paddr`: `data` 的物理基址
/// - `desc_table`: 供驱动填写的 ADMA2 描述符表暂存区
/// - `desc_paddr`: `desc_table` 的物理基址(须 8 字节对齐)
///
/// # Safety
/// 调用者必须确保 `data_paddr` / `desc_paddr` 与对应切片指向同一段、
/// 在 DMA 引擎视角下有效的物理内存,否则会造成内存破坏。
pub unsafe fn read_blocks_adma2(
&self,
start_block: u32,
data: &mut [u8],
data_paddr: u64,
desc_table: &mut [Adma2Desc],
desc_paddr: u64,
) -> Result<(), CmdError> {
// 发起 DMA 传输(配置描述符/寄存器并下发 CMD18)。
unsafe {
self.adma2_issue(start_block, data, data_paddr, desc_table, desc_paddr)?;
}
// 忙等待传输完成。
let res = self.wait_for_xfer_done();
// 收尾:清中断状态 + invalidate 数据缓冲。
self.finish_dma_read(data);
res
}
/// **非阻塞** 地发起一次 ADMA2 多块 DMA 读取:配置描述符表与寄存器、
/// 下发 CMD18(等待命令完成 CMD_CMPL 后立即返回),数据由 DMA 引擎在
/// 后台写入内存。调用方随后用 [`Sdmmc::poll_xfer_done`] 轮询是否结束,
/// 结束后必须调用 [`Sdmmc::finish_dma_read`] 收尾。
///
/// 该接口用于「传输期间让 CPU 去做其他工作」的场景(例如测量 CPU 可用率)。
///
/// 参数含义同 [`Sdmmc::read_blocks_adma2`]。
///
/// # Safety
/// 同 [`Sdmmc::read_blocks_adma2`]:物理地址须与切片指向同一段有效 DMA 内存。
pub unsafe fn read_blocks_adma2_start(
&self,
start_block: u32,
data: &mut [u8],
data_paddr: u64,
desc_table: &mut [Adma2Desc],
desc_paddr: u64,
) -> Result<(), CmdError> {
unsafe { self.adma2_issue(start_block, data, data_paddr, desc_table, desc_paddr) }
}
/// 轮询 DMA 数据传输是否完成(**不清除** 完成标志,也不做 cache 维护)。
///
/// - `Ok(true)`:传输完成,可调用 [`Sdmmc::finish_dma_read`] 收尾。
/// - `Ok(false)`:仍在传输中。
/// - `Err(_)`:发生错误中断(已清除错误标志)。
pub fn poll_xfer_done(&self) -> Result<bool, CmdError> {
let sts = self.regs.norm_and_err_int_sts.extract();
if sts.is_set(NORM_AND_ERR_INT_STS::XFER_CMPL) {
return Ok(true);
}
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);
}
Ok(false)
}
/// DMA 读取收尾:清除中断状态寄存器,并 invalidate 数据缓冲区,
/// 保证 CPU 后续读到的是 DMA 写入内存的新数据。
pub fn finish_dma_read(&self, data: &mut [u8]) {
self.regs
.norm_and_err_int_sts
.set(self.regs.norm_and_err_int_sts.get());
dcache_invalidate_range(data.as_ptr() as usize, data.len());
}
/// 内部:构建 ADMA2 描述符表、维护 cache、配置寄存器并下发 CMD18。
///
/// 返回时命令已完成 (CMD_CMPL),DMA 数据传输在后台进行。
///
/// # Safety
/// 见 [`Sdmmc::read_blocks_adma2`]。
unsafe fn adma2_issue(
&self,
start_block: u32,
data: &[u8],
data_paddr: u64,
desc_table: &mut [Adma2Desc],
desc_paddr: u64,
) -> Result<(), CmdError> {
assert!(!data.is_empty(), "adma2: buffer must not be empty");
assert!(
data.len().is_multiple_of(BLOCK_SIZE),
"adma2: buffer length must be a multiple of BLOCK_SIZE"
);
let total_len = data.len();
let blk_cnt = total_len / BLOCK_SIZE;
// 计算所需描述符数量并填表:把整段缓冲拆分成 <= ADMA2_MAX_PER_DESC 的片段。
let desc_needed = total_len.div_ceil(ADMA2_MAX_PER_DESC);
assert!(
desc_table.len() >= desc_needed,
"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;
}
// 让控制器看到最新的描述符表内容(写回 D-cache)。
let desc_bytes = desc_needed * core::mem::size_of::<Adma2Desc>();
dcache_clean_range(desc_table.as_ptr() as usize, desc_bytes);
// DMA 写内存前先 clean 数据缓冲区,避免脏行在传输期间被回写覆盖 DMA 数据。
dcache_clean_range(data.as_ptr() as usize, total_len);
// 选择 ADMA2 作为 DMA 模式,并写入描述符表物理基址。
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);
// 发送 CMD18(内部会配置块大小/块数、AUTO_CMD12,并因 dma=true 置 DMA_EN)。
// cmd_transfer 返回时 CMD_CMPL 已完成,数据传输在后台由 DMA 引擎进行。
self.cmd_transfer(CommandType::CMD(18), start_block, blk_cnt as _, true)
}
/// 从数据缓冲区读取数据(PIO)。
///
/// # 参数
/// - `data`: 用于存储读取数据的缓冲区,长度须为 [`BLOCK_SIZE`] 的整数倍
fn read_buff(&self, data: &mut [u8]) -> Result<(), CmdError> {
assert!(data.len().is_multiple_of(BLOCK_SIZE));
for block_buf in data.chunks_exact_mut(BLOCK_SIZE) {
// 等待缓冲区读就绪
loop {
let sts = self.regs.norm_and_err_int_sts.extract();
if sts.is_set(NORM_AND_ERR_INT_STS::BUF_RRDY) {
// 清除缓冲区读就绪标志
self.regs
.norm_and_err_int_sts
.write(NORM_AND_ERR_INT_STS::BUF_RRDY::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_mut(4) {
let value = self.regs.buf_data_port.get();
chunk.copy_from_slice(&value.to_le_bytes());
delay(1);
}
}
Ok(())
}
}