libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
ch32_flash.cpp
1// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
2#include "ch32_flash.hpp"
3
4using namespace LibXR;
5
6// WCH GCC15 is sensitive to the exact self-programming code shape on the
7// currently validated CH32V2/V3 flash paths.
8// Keep the erase/write hot loops behind hard noinline boundaries, but leave the
9// surrounding unlock/clock/flag choreography in the original call sites.
10// WCH GCC15 对当前已验证可用的 CH32V2/V3 自擦写路径代码形状很敏感。
11// 这里把擦除/写入热循环放到明确的 noinline 边界后面,
12// 但解锁、降频、清标志这些外围时序仍留在原来的调用点。
13extern "C" __attribute__((noinline)) ErrorCode CH32FlashWriteHotPath(uint32_t start_addr,
14 uint32_t end_addr,
15 const uint8_t* src);
16
17namespace
18{
19using CH32FlashEraseHotPathFn = ErrorCode (*)(uint32_t erase_begin, uint32_t erase_end);
20using CH32FlashWriteHotLoopFn = ErrorCode (*)(uint32_t start_addr, uint32_t end_addr,
21 const uint8_t* src);
22using CH32FlashWritePageFn = ErrorCode (*)(uint32_t start_addr, uint32_t end_addr,
23 const uint8_t* src);
24
25struct CH32FlashHotPaths
26{
27 CH32FlashEraseHotPathFn erase = nullptr;
28 CH32FlashWriteHotLoopFn write_halfword = nullptr;
29 CH32FlashWritePageFn write_page = nullptr;
30};
31
32constexpr size_t routine_align = 16u;
33constexpr size_t ch32_flash_erase_hot_path_size = 0x78u;
34constexpr size_t ch32_flash_write_hot_loop_size = 0xD2u;
35constexpr size_t ch32_flash_write_page_size = 0x13Cu;
36
37static CH32FlashHotPaths g_ch32_flash_hot_paths;
38static void InitHotPathsOnce();
39} // namespace
40
41// 访问时钟切半
42static void flash_set_access_clock_half_sysclk(void)
43{
44 FLASH_Unlock(); // Unlock FPEC/CTL.
45 FLASH->CTLR &= ~(1u << 25); // SCKMOD=0 => SYSCLK/2.
46 FLASH_Lock(); // Optional relock.
47}
48
49// 访问时钟还原
50static void flash_set_access_clock_sysclk(void)
51{
52 FLASH_Unlock();
53 FLASH->CTLR |= (1u << 25); // SCKMOD=1 => access clock = SYSCLK.
54 FLASH_Lock();
55}
56
57static inline void flash_exit_enhanced_read_if_enabled()
58{
59 if (FLASH->STATR & (1u << 7))
60 { // EHMODS=1?
61 FLASH_Unlock();
62 FLASH->CTLR &= ~(1u << 24); // EHMOD=0.
63 FLASH->CTLR |= (1u << 22); // RSENACT=1 (write-only, auto-cleared by hardware).
64 FLASH_Lock();
65 }
66}
67
68static inline void flash_fast_unlock()
69{
70 FLASH_Unlock(); // Regular unlock sequence (KEYR).
71 // Fast-mode unlock sequence (MODEKEYR KEY1/KEY2).
72 FLASH->MODEKEYR = 0x45670123u;
73 FLASH->MODEKEYR = 0xCDEF89ABu;
74}
75
76static inline void flash_fast_lock()
77{
78 FLASH_Unlock();
79 FLASH->CTLR |= (1u << 15); // FLOCK=1.
80 FLASH_Lock();
81}
82
83static inline void flash_clear_flags_once()
84{
85#ifdef FLASH_FLAG_BSY
86 FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR);
87#else
88 FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR);
89#endif
90}
91
92// Encapsulate one flash program/erase session:
93// - exit enhanced read mode
94// - slow flash access clock
95// - disable IRQ + unlock fast mode + clear status
96// - restore lock/IRQ/clock in destructor
98{
99 public:
101 {
102 flash_exit_enhanced_read_if_enabled();
103 flash_set_access_clock_half_sysclk();
104 __disable_irq();
105 flash_fast_unlock();
106 flash_clear_flags_once();
107 }
108
110 {
111 flash_fast_lock();
112 __enable_irq();
113 flash_set_access_clock_sysclk();
114 }
115
116 FlashAccessSession(const FlashAccessSession&) = delete;
117 FlashAccessSession& operator=(const FlashAccessSession&) = delete;
118};
119
120inline void CH32Flash::ClearFlashFlagsOnce() { flash_clear_flags_once(); }
121
122CH32Flash::CH32Flash(const FlashSector* sectors, size_t sector_count, size_t start_sector)
123 : Flash(sectors[start_sector - 1].size, MinWriteSize(),
124 {reinterpret_cast<void*>(sectors[start_sector - 1].address),
125 sectors[sector_count - 1].address - sectors[start_sector - 1].address +
126 sectors[sector_count - 1].size}),
127 sectors_(sectors),
128 base_address_(sectors[start_sector - 1].address),
129 sector_count_(sector_count)
130{
131 // `Flash` 基类看到的是从 `start_sector` 开始的一整段连续逻辑窗口,
132 // `sectors_` 仍保留原始物理扇区表,用于范围检查和地址换算。
133 // The `Flash` base class sees one contiguous logical window starting at
134 // `start_sector`, while `sectors_` still preserves the physical sector table
135 // for bounds checks and address translation.
136 InitHotPathsOnce();
137}
138
139ErrorCode CH32Flash::Erase(size_t offset, size_t size)
140{
141 if (size == 0)
142 {
143 return ErrorCode::ARG_ERR;
144 }
145
146 ASSERT(SystemCoreClock <= 120000000);
147
148 const uint32_t START_ADDR = base_address_ + static_cast<uint32_t>(offset);
149 if (!IsInRange(START_ADDR, size))
150 {
152 }
153 const uint32_t END_ADDR = START_ADDR + static_cast<uint32_t>(size);
154 const auto erase_hot_path = g_ch32_flash_hot_paths.erase;
155 ASSERT(erase_hot_path != nullptr);
156 FlashAccessSession session;
157
158 // 当前已验证的 CH32V2/V3 快速擦除路径都按 256B 页对齐工作,
159 // 因此子页范围的擦除请求进入 SRAM 热路径前必须先扩到整页边界。
160 // The currently validated CH32V2/V3 fast erase path works on 256-byte pages,
161 // so a sub-page erase request must be widened to page boundaries before
162 // entering the SRAM hot path.
163 const uint32_t fast_size = 256u;
164 const uint32_t ERASE_BEGIN = START_ADDR & ~(fast_size - 1u);
165 const uint32_t ERASE_END = (END_ADDR + fast_size - 1u) & ~(fast_size - 1u);
166 if (ERASE_END <= ERASE_BEGIN)
167 {
168 return ErrorCode::OK;
169 }
170
171 return erase_hot_path(ERASE_BEGIN, ERASE_END);
172}
173
175{
176 ASSERT(SystemCoreClock <= 120000000);
177
178 if (!data.addr_ || data.size_ == 0)
179 {
180 ASSERT(false);
181 return ErrorCode::ARG_ERR;
182 }
183
184 const uint32_t START_ADDR = base_address_ + static_cast<uint32_t>(offset);
185 if (!IsInRange(START_ADDR, data.size_))
186 {
187 ASSERT(false);
189 }
190
191 const uint8_t* src = reinterpret_cast<const uint8_t*>(data.addr_);
192 const uint32_t END_ADDR = START_ADDR + static_cast<uint32_t>(data.size_);
193 return CH32FlashWriteHotPath(START_ADDR, END_ADDR, src);
194}
195
196extern "C" __attribute__((noinline)) ErrorCode CH32FlashWriteHotPath(uint32_t start_addr,
197 uint32_t end_addr,
198 const uint8_t* src)
199{
200 const auto write_hot_loop = g_ch32_flash_hot_paths.write_halfword;
201 const auto write_page = g_ch32_flash_hot_paths.write_page;
202 constexpr uint32_t page_size = 256u;
203 ASSERT(write_hot_loop != nullptr && write_page != nullptr);
204 FlashAccessSession session;
206
207 // 写入路径拆成三段:
208 // 1) 非对齐 head 走半字循环
209 // 2) 对齐的整 256B 页走整页编程
210 // 3) 非对齐 tail 再回到半字循环
211 // Split the write into:
212 // 1) unaligned head halfword loop
213 // 2) fully aligned 256-byte page program loop
214 // 3) unaligned tail halfword loop
215 const uint32_t aligned_begin = (start_addr + page_size - 1u) & ~(page_size - 1u);
216 const uint32_t aligned_end = end_addr & ~(page_size - 1u);
217
218 const uint32_t head_end = (aligned_begin < end_addr) ? aligned_begin : end_addr;
219 if (start_addr < head_end)
220 {
221 ec = write_hot_loop(start_addr, head_end, src);
222 }
223
224 const bool has_full_pages = (aligned_begin < aligned_end);
225 if (ec == ErrorCode::OK && has_full_pages)
226 {
227 ec = write_page(aligned_begin, aligned_end, src + (aligned_begin - start_addr));
228 }
229
230 // Tail begins after the already-consumed head/body segments.
231 // For single-page unaligned writes, `head_end` may already reach `end_addr`,
232 // so the tail must start from `head_end` instead of replaying the same span.
233 // 尾段必须从已经消费完的 head/body 之后开始。
234 // 对“同页内的非对齐小写入”,`head_end` 可能已经等于 `end_addr`,
235 // 这里不能再从 `start_addr` 重放同一段。
236 const uint32_t tail_begin = has_full_pages ? aligned_end : head_end;
237 if (ec == ErrorCode::OK && tail_begin < end_addr)
238 {
239 ec = write_hot_loop(tail_begin, end_addr, src + (tail_begin - start_addr));
240 }
241
242 return ec;
243}
244
245bool CH32Flash::IsInRange(uint32_t addr, size_t size) const
246{
247 const uint32_t BEGIN = base_address_;
248 const uint32_t LIMIT =
249 sectors_[sector_count_ - 1].address + sectors_[sector_count_ - 1].size;
250 const uint32_t END = addr + static_cast<uint32_t>(size);
251 return (addr >= BEGIN) && (END <= LIMIT) && (END >= addr);
252}
253
254namespace
255{
256// Verified CH32 flash hot-path machine code blob kept directly in SRAM.
257// The current validated coverage includes CH32V2/V3 targets exercised in this
258// review line.
259// `.S` remains in the tree as a readable source/reference for future toolchain
260// refreshes, but runtime no longer depends on reassembling and memcpy-ing it.
261// 已验证的 CH32 flash 热路径机器码 blob 直接常驻 SRAM。
262// 这条 review 线当前的实测覆盖包含 CH32V2/V3 目标。
263// `.S` 仍保留在树里作为可读参考,运行时不再依赖重新汇编再 memcpy。
264// clang-format off
265alignas(routine_align) static uint8_t g_ch32_flash_erase_hot[ch32_flash_erase_hot_path_size] = {
266 0x63, 0x79, 0xb5, 0x04, 0x01, 0x76, 0x7d, 0x16, 0xb7, 0x26, 0x02, 0x40, 0x37, 0x08,
267 0x02, 0x00, 0x93, 0x08, 0x00, 0x02, 0x9c, 0x4a, 0x37, 0x47, 0x0f, 0x00, 0x13, 0x07,
268 0x17, 0x24, 0xb3, 0xe7, 0x07, 0x01, 0x9c, 0xca, 0xc8, 0xca, 0x9c, 0x4a, 0x93, 0xe7,
269 0x07, 0x04, 0x9c, 0xca, 0x11, 0xa0, 0x1d, 0xc3, 0xdc, 0x46, 0x7d, 0x17, 0x85, 0x8b,
270 0xe5, 0xff, 0xdc, 0x46, 0xc1, 0x8b, 0x9d, 0xe3, 0x9c, 0x4a, 0x13, 0x05, 0x05, 0x10,
271 0xf1, 0x8f, 0x9c, 0xca, 0x23, 0xa6, 0x16, 0x01, 0xe3, 0x63, 0xb5, 0xfc, 0x01, 0x45,
272 0x82, 0x80, 0x9c, 0x4a, 0x01, 0x77, 0x7d, 0x17, 0xf9, 0x8f, 0x9c, 0xca, 0x51, 0x55,
273 0x82, 0x80, 0x9c, 0x4a, 0x01, 0x77, 0x7d, 0x17, 0xf9, 0x8f, 0x9c, 0xca, 0x93, 0x07,
274 0x00, 0x03, 0xdc, 0xc6, 0x71, 0x55, 0x82, 0x80};
275
276alignas(routine_align) static uint8_t g_ch32_flash_write_hot[ch32_flash_write_hot_loop_size] = {
277 0x13, 0x78, 0xe5, 0xff, 0x13, 0x8e, 0x15, 0x00, 0xb3, 0x07, 0xa8, 0x40, 0xc9, 0x7e,
278 0x13, 0x7e, 0xee, 0xff, 0x33, 0x03, 0xf6, 0x00, 0x93, 0x68, 0x15, 0x00, 0x93, 0x8e,
279 0x7e, 0xcc, 0xb7, 0x26, 0x02, 0x40, 0x13, 0x0f, 0x00, 0x02, 0x63, 0x72, 0xc8, 0x09,
280 0x83, 0x57, 0x08, 0x00, 0xc2, 0x07, 0xc1, 0x83, 0x63, 0x6e, 0xa8, 0x06, 0x63, 0x7c,
281 0xb8, 0x06, 0x03, 0x47, 0x03, 0x00, 0x13, 0xf6, 0x07, 0xf0, 0x59, 0x8e, 0x63, 0xfa,
282 0xb8, 0x00, 0x03, 0x47, 0x13, 0x00, 0x13, 0x76, 0xf6, 0x0f, 0x13, 0x77, 0xf7, 0x0f,
283 0x22, 0x07, 0x59, 0x8e, 0x63, 0x04, 0xf6, 0x04, 0x13, 0xc7, 0xf7, 0xff, 0x71, 0x8f,
284 0x19, 0xc3, 0xf6, 0x97, 0xb5, 0xe3, 0x9c, 0x4a, 0x37, 0x47, 0x0f, 0x00, 0x13, 0x07,
285 0x17, 0x24, 0x93, 0xe7, 0x17, 0x00, 0x9c, 0xca, 0x23, 0x10, 0xc8, 0x00, 0x11, 0xa0,
286 0x15, 0xcf, 0xdc, 0x46, 0x7d, 0x17, 0x85, 0x8b, 0xe5, 0xff, 0x9c, 0x4a, 0xf9, 0x9b,
287 0x9c, 0xca, 0xdc, 0x46, 0xc1, 0x8b, 0x8d, 0xeb, 0x83, 0x57, 0x08, 0x00, 0x63, 0x9b,
288 0xc7, 0x02, 0x23, 0xa6, 0xe6, 0x01, 0x09, 0x08, 0x89, 0x08, 0x09, 0x03, 0xe3, 0x62,
289 0xc8, 0xf9, 0x01, 0x45, 0x82, 0x80, 0xe3, 0xf9, 0xb8, 0xfe, 0xe3, 0xe7, 0xa8, 0xfe,
290 0x3e, 0x86, 0x41, 0xbf, 0x9c, 0x4a, 0x51, 0x55, 0xf9, 0x9b, 0x9c, 0xca, 0x82, 0x80,
291 0x93, 0x07, 0x00, 0x03, 0xdc, 0xc6, 0x71, 0x55, 0x82, 0x80, 0x69, 0x55, 0x82, 0x80};
292
293alignas(routine_align) static uint8_t g_ch32_flash_write_page[ch32_flash_write_page_size] = {
294 0x63, 0x77, 0xb5, 0x12, 0x13, 0x0e, 0x06, 0x10, 0xb7, 0x28, 0x02, 0x40, 0xc1, 0x6e,
295 0x83, 0xa7, 0x08, 0x01, 0x37, 0x47, 0x0f, 0x00, 0x13, 0x07, 0x17, 0x24, 0xb3, 0xe7,
296 0xd7, 0x01, 0x23, 0xa8, 0xf8, 0x00, 0x11, 0xa0, 0x79, 0xcf, 0x83, 0xa7, 0xc8, 0x00,
297 0x7d, 0x17, 0x85, 0x8b, 0xfd, 0xfb, 0x37, 0x47, 0x0f, 0x00, 0x13, 0x07, 0x17, 0x24,
298 0xb7, 0x26, 0x02, 0x40, 0x11, 0xa0, 0x5d, 0xcb, 0xdc, 0x46, 0x7d, 0x17, 0x89, 0x8b,
299 0xe5, 0xff, 0x32, 0x88, 0xb3, 0x0f, 0xc5, 0x40, 0xb7, 0x26, 0x02, 0x40, 0x83, 0x47,
300 0x18, 0x00, 0x03, 0x43, 0x28, 0x00, 0x03, 0x4f, 0x08, 0x00, 0x03, 0x47, 0x38, 0x00,
301 0x42, 0x03, 0xa2, 0x07, 0xb3, 0xe7, 0x67, 0x00, 0x62, 0x07, 0xb3, 0xe7, 0xe7, 0x01,
302 0xd9, 0x8f, 0x33, 0x83, 0x0f, 0x01, 0x37, 0x47, 0x0f, 0x00, 0x23, 0x20, 0xf3, 0x00,
303 0x13, 0x07, 0x17, 0x24, 0x11, 0xa0, 0x25, 0xcb, 0xdc, 0x46, 0x7d, 0x17, 0x89, 0x8b,
304 0xe5, 0xff, 0x11, 0x08, 0xe3, 0x11, 0x0e, 0xfd, 0x9c, 0x4a, 0x37, 0x08, 0x20, 0x00,
305 0x37, 0x47, 0x0f, 0x00, 0xb3, 0xe7, 0x07, 0x01, 0x9c, 0xca, 0x13, 0x07, 0x17, 0x24,
306 0xb7, 0x26, 0x02, 0x40, 0x11, 0xa0, 0x39, 0xc3, 0xdc, 0x46, 0x7d, 0x17, 0x85, 0x8b,
307 0xe5, 0xff, 0x9c, 0x4a, 0x41, 0x77, 0x7d, 0x17, 0xf9, 0x8f, 0x9c, 0xca, 0xdc, 0x46,
308 0xc1, 0x8b, 0xb5, 0xe7, 0x32, 0x87, 0x19, 0xa0, 0x63, 0x04, 0xc7, 0x05, 0x83, 0x47,
309 0x17, 0x00, 0x03, 0x48, 0x07, 0x00, 0xb3, 0x06, 0xf7, 0x01, 0xa2, 0x07, 0xb3, 0xe7,
310 0x07, 0x01, 0x83, 0xd6, 0x06, 0x00, 0xc2, 0x07, 0xc1, 0x83, 0x09, 0x07, 0xe3, 0x80,
311 0xd7, 0xfe, 0x69, 0x55, 0x82, 0x80, 0x9c, 0x4a, 0x41, 0x77, 0x7d, 0x17, 0xf9, 0x8f,
312 0x9c, 0xca, 0x51, 0x55, 0x82, 0x80, 0x83, 0xa7, 0x08, 0x01, 0x41, 0x77, 0x7d, 0x17,
313 0xf9, 0x8f, 0x23, 0xa8, 0xf8, 0x00, 0x51, 0x55, 0x82, 0x80, 0xb7, 0x27, 0x02, 0x40,
314 0x13, 0x07, 0x00, 0x02, 0x13, 0x05, 0x05, 0x10, 0xd8, 0xc7, 0x13, 0x0e, 0x0e, 0x10,
315 0x13, 0x06, 0x06, 0x10, 0xe3, 0x62, 0xb5, 0xee, 0x01, 0x45, 0x82, 0x80, 0x93, 0x07,
316 0x00, 0x03, 0xdc, 0xc6, 0x71, 0x55, 0x82, 0x80};
317// clang-format on
318
319static void InitHotPathsOnce()
320{
321 static bool inited = false;
322 if (inited)
323 {
324 return;
325 }
326
327 // 这些 blob 被当作固定运行时产物使用;只要字节数漂移,
328 // 下面的函数指针转换就会立刻失效。
329 // These blobs are treated as fixed runtime artifacts. If any byte count
330 // drifts, the corresponding function pointer cast below becomes invalid
331 // immediately.
332 static_assert(sizeof(g_ch32_flash_erase_hot) == ch32_flash_erase_hot_path_size);
333 static_assert(sizeof(g_ch32_flash_write_hot) == ch32_flash_write_hot_loop_size);
334 static_assert(sizeof(g_ch32_flash_write_page) == ch32_flash_write_page_size);
335
336 // 这些 blob 实际驻留在可写 SRAM 数组里,因此在发布函数指针前
337 // 需要先做一次指令缓存刷新。
338 // The blobs live in writable SRAM arrays, so flush the instruction cache once
339 // before publishing the function pointers.
340 __builtin___clear_cache(
341 reinterpret_cast<char*>(g_ch32_flash_erase_hot),
342 reinterpret_cast<char*>(g_ch32_flash_erase_hot + sizeof(g_ch32_flash_erase_hot)));
343 __builtin___clear_cache(
344 reinterpret_cast<char*>(g_ch32_flash_write_hot),
345 reinterpret_cast<char*>(g_ch32_flash_write_hot + sizeof(g_ch32_flash_write_hot)));
346 __builtin___clear_cache(
347 reinterpret_cast<char*>(g_ch32_flash_write_page),
348 reinterpret_cast<char*>(g_ch32_flash_write_page + sizeof(g_ch32_flash_write_page)));
349
350 g_ch32_flash_hot_paths.erase =
351 reinterpret_cast<CH32FlashEraseHotPathFn>(g_ch32_flash_erase_hot);
352 g_ch32_flash_hot_paths.write_halfword =
353 reinterpret_cast<CH32FlashWriteHotLoopFn>(g_ch32_flash_write_hot);
354 g_ch32_flash_hot_paths.write_page =
355 reinterpret_cast<CH32FlashWritePageFn>(g_ch32_flash_write_page);
356 inited = true;
357}
358} // namespace
359
360// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
CH32Flash(const FlashSector *sectors, size_t sector_count, size_t start_sector)
构造闪存对象 / Construct flash object
ErrorCode Erase(size_t offset, size_t size) override
Erases a section of the flash memory. 擦除闪存的指定区域。
ErrorCode Write(size_t offset, ConstRawData data) override
Writes data to the flash memory. 向闪存写入数据。
只读原始数据视图 / Immutable raw data view
size_t size_
数据字节数 / Data size in bytes
const void * addr_
数据起始地址 / Data start address
Abstract base class representing a flash memory interface. 抽象基类,表示闪存接口。
Definition flash.hpp:19
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ OUT_OF_RANGE
超出范围 | Out of range
@ OK
操作成功 | Operation successful
@ ARG_ERR
参数错误 | Argument error
闪存扇区描述 / Flash sector descriptor
uint32_t size
扇区大小(字节) / Sector size in bytes
uint32_t address
扇区起始地址 / Sector base address