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