libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
ch32_usb_otghs.cpp
1// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
2// ch32_usb_otghs.cpp (OTG HS)
3#include "ch32_usb_dev.hpp"
4#include "ch32_usb_endpoint.hpp"
5#include "ch32_usb_rcc.hpp"
6#include "ep.hpp"
7
8using namespace LibXR;
9using namespace LibXR::USB;
10
11#if defined(USBHSD)
12
13namespace
14{
15
16using OtgHsEndpointMap = decltype(LibXR::CH32EndpointOtgHs::map_otg_hs_);
17
18constexpr uint8_t OUT_IDX = static_cast<uint8_t>(LibXR::USB::Endpoint::Direction::OUT);
19constexpr uint8_t IN_IDX = static_cast<uint8_t>(LibXR::USB::Endpoint::Direction::IN);
20
21static void ResetEp0State(OtgHsEndpointMap& map)
22{
23 auto* out0 = map[0][OUT_IDX];
24 auto* in0 = map[0][IN_IDX];
25
26 ASSERT(out0 != nullptr);
27 ASSERT(in0 != nullptr);
28
30 out0->tog0_ = true;
31 out0->tog1_ = false;
33 in0->tog0_ = true;
34 in0->tog1_ = false;
35
36 // After reset/suspend recovery, EP0 must be ready for the next setup packet.
37 // reset/suspend 恢复后,EP0 必须回到等待下一包 setup 的状态。
38 USBHSD->UEP0_TX_CTRL = USBHS_UEP_T_TOG_DATA1 | USBHS_UEP_T_RES_NAK;
39 USBHSD->UEP0_RX_CTRL = USBHS_UEP_R_TOG_DATA1 | USBHS_UEP_R_RES_ACK;
40}
41
42static bool CompleteEp0InBeforeSetup(OtgHsEndpointMap& map, uint8_t int_flag,
43 uint8_t int_st)
44{
45 if ((int_flag & USBHS_UIF_SETUP_ACT) == 0u || (int_flag & USBHS_UIF_TRANSFER) == 0u)
46 {
47 return false;
48 }
49 if ((int_st & USBHS_UIS_ENDP_MASK) != 0u ||
50 (int_st & USBHS_UIS_TOKEN_MASK) != USBHS_UIS_TOKEN_IN)
51 {
52 return false;
53 }
54
55 auto* in0 = map[0][IN_IDX];
56 ASSERT(in0 != nullptr);
57 if (in0->GetState() != LibXR::USB::Endpoint::State::BUSY)
58 {
59 return false;
60 }
61
62 // Some setup races report "new setup" and "previous EP0 IN complete" together.
63 // Consume the old EP0 IN completion first, then let setup rebuild the control state.
64 // 部分 setup 竞争会把“新 setup 到来”和“上一笔 EP0 IN 完成”同时上报;
65 // 这里先消费旧的 EP0 IN 完成,再让 setup 重建控制传输状态。
66 in0->TransferComplete(0u);
67 return true;
68}
69
70static void PrepareEp0ForSetup(OtgHsEndpointMap& map)
71{
72 auto* out0 = map[0][OUT_IDX];
73 auto* in0 = map[0][IN_IDX];
74
75 ASSERT(out0 != nullptr);
76 ASSERT(in0 != nullptr);
77
78 // A fresh SETUP cancels the previous control transfer, so EP0 must be re-armed
79 // to the default control endpoint shape before the setup packet is dispatched.
80 // 新的 SETUP 会中断前一笔控制传输,因此在分发 setup 包之前,
81 // 必须先把 EP0 恢复成默认控制端点形态。
84 out0->tog0_ = true;
85 out0->tog1_ = false;
86 in0->tog0_ = true;
87 in0->tog1_ = false;
88 USBHSD->UEP0_TX_CTRL = USBHS_UEP_T_TOG_DATA1 | USBHS_UEP_T_RES_NAK;
89 USBHSD->UEP0_RX_CTRL = USBHS_UEP_R_TOG_DATA1 | USBHS_UEP_R_RES_NAK;
90}
91
92static void HandleTransferOut(CH32EndpointOtgHs* ep_out, uint8_t ep_num, uint8_t int_st)
93{
94 if (ep_out == nullptr)
95 {
96 return;
97 }
98
99 const bool is_nak = ((int_st & USBHS_UIS_IS_NAK) != 0u);
100 const bool busy = (ep_out->GetState() == LibXR::USB::Endpoint::State::BUSY);
101 const uint16_t rx_len = USBHSD->RX_LEN;
102
103 if (ep_num == 0u)
104 {
105 if (busy && ((rx_len > 0u) || !is_nak))
106 {
107 ep_out->TransferComplete(rx_len);
108 }
109 return;
110 }
111
112 if (!is_nak)
113 {
114 ep_out->TransferComplete(rx_len);
115 return;
116 }
117 if (!busy || rx_len == 0u)
118 {
119 return;
120 }
121
122 // Some non-EP0 OUT paths may still expose a usable late length snapshot while the
123 // endpoint stays BUSY. Keep that narrow recovery path here.
124 // 部分 non-EP0 OUT 路径在端点仍处于 BUSY 时,仍可能给出可用的晚到长度快照;
125 // 这里只保留这条窄恢复路径。
126 ep_out->TransferComplete(rx_len);
127}
128
129static void HandleTransferIn(CH32EndpointOtgHs* ep_in, uint8_t ep_num, uint8_t int_st)
130{
131 if (ep_in == nullptr)
132 {
133 return;
134 }
135
136 const bool is_nak = ((int_st & USBHS_UIS_IS_NAK) != 0u);
137 if (!is_nak)
138 {
139 ep_in->TransferComplete(0u);
140 return;
141 }
142
143 const bool busy = (ep_in->GetState() == LibXR::USB::Endpoint::State::BUSY);
144 const bool ep0_data_in_complete =
145 (ep_num == 0u) && busy && (ep_in->last_transfer_size_ > 0u);
146 if (!ep0_data_in_complete)
147 {
148 return;
149 }
150
151 // EP0 data IN may complete after setup arbitration without a visible ACK.
152 // Status ZLP must still stay strict, so only non-zero data stages use this path.
153 // EP0 的数据 IN 在 setup 仲裁后也可能没有可见 ACK 就结束;
154 // 但 status ZLP 仍然必须保持严格,因此这里只允许非零数据阶段走这条路径。
155 ep_in->TransferComplete(0u);
156}
157
158static void HandleTransferToken(OtgHsEndpointMap& map, uint8_t int_st)
159{
160 const uint8_t token = int_st & USBHS_UIS_TOKEN_MASK;
161 const uint8_t ep_num = int_st & USBHS_UIS_ENDP_MASK;
162 auto& ep = map[ep_num];
163
164 switch (token)
165 {
166 case USBHS_UIS_TOKEN_SETUP:
167 {
168 // CH32V30x USBHS reports SETUP through SETUP_ACT, so TOKEN_SETUP is
169 // informational only here and must not re-run the setup path.
170 // CH32V30x USBHS 通过 SETUP_ACT 上报 SETUP,因此这里的 TOKEN_SETUP
171 // 只是信息位,不能再次重复执行 setup 路径。
172 break;
173 }
174
175 case USBHS_UIS_TOKEN_OUT:
176 HandleTransferOut(ep[OUT_IDX], ep_num, int_st);
177 break;
178
179 case USBHS_UIS_TOKEN_IN:
180 HandleTransferIn(ep[IN_IDX], ep_num, int_st);
181 break;
182
183 default:
184 break;
185 }
186}
187
188} // namespace
189
190// NOLINTNEXTLINE(readability-identifier-naming)
191extern "C" __attribute__((interrupt("WCH-Interrupt-fast"))) void USBHS_IRQHandler(void)
192{
193 auto& map = LibXR::CH32EndpointOtgHs::map_otg_hs_;
194
195 // Handle order matters: recover bus-level events first, then settle EP0 setup
196 // arbitration, and finally dispatch ordinary token completions.
197 // 处理顺序很重要:先恢复总线级事件,再收束 EP0 setup 仲裁,
198 // 最后分发普通 token 完成事件。
199 while (true)
200 {
201 const uint16_t INTFGST = *reinterpret_cast<volatile uint16_t*>(
202 reinterpret_cast<uintptr_t>(&USBHSD->INT_FG));
203
204 const uint8_t INTFLAG = static_cast<uint8_t>(INTFGST & 0x00FFu);
205 const uint8_t INTST = static_cast<uint8_t>((INTFGST >> 8) & 0x00FFu);
206
207 if (INTFLAG == 0)
208 {
209 break;
210 }
211
212 uint8_t clear_mask = 0;
213
214 // 1) Bus-level recovery: reset/suspend must rebuild EP0 first.
215 // 1) 总线级恢复:reset/suspend 必须优先重建 EP0。
216 if (INTFLAG & USBHS_UIF_BUS_RST)
217 {
218 USBHSD->DEV_AD = 0;
219 LibXR::CH32USBOtgHS::self_->Deinit(true);
220 LibXR::CH32USBOtgHS::self_->Init(true);
221 ResetEp0State(map);
222 clear_mask |= USBHS_UIF_BUS_RST;
223 }
224
225 if (INTFLAG & USBHS_UIF_SUSPEND)
226 {
227 LibXR::CH32USBOtgHS::self_->Deinit(true);
228 LibXR::CH32USBOtgHS::self_->Init(true);
229 ResetEp0State(map);
230 clear_mask |= USBHS_UIF_SUSPEND;
231 }
232
233 // 2) Setup arbitration: consume the previous EP0 IN completion before the
234 // new setup resets the control-transfer state.
235 // 2) Setup 仲裁:在新 setup 重置控制传输状态之前,
236 // 先消费上一笔 EP0 IN 完成。
237 if (CompleteEp0InBeforeSetup(map, INTFLAG, INTST))
238 {
239 clear_mask |= USBHS_UIF_TRANSFER;
240 }
241
242 if (INTFLAG & USBHS_UIF_SETUP_ACT)
243 {
244 PrepareEp0ForSetup(map);
245
246 auto* out0 = map[0][OUT_IDX];
247 ASSERT(out0 != nullptr);
248 const auto* setup =
249 reinterpret_cast<const LibXR::USB::SetupPacket*>(out0->GetBuffer().addr_);
250 LibXR::CH32USBOtgHS::self_->OnSetupPacket(true, setup);
251 clear_mask |= USBHS_UIF_SETUP_ACT;
252 }
253
254 // 3) Ordinary token completion: non-setup transfers arrive here.
255 // 3) 普通 token 完成:非 setup 传输统一在这里分发。
256 if ((INTFLAG & USBHS_UIF_TRANSFER) && ((clear_mask & USBHS_UIF_TRANSFER) == 0u))
257 {
258 HandleTransferToken(map, INTST);
259 clear_mask |= USBHS_UIF_TRANSFER;
260 }
261
262 clear_mask |= static_cast<uint8_t>(INTFLAG & ~(clear_mask));
263 USBHSD->INT_FG = clear_mask;
264 }
265}
266
267CH32USBOtgHS::CH32USBOtgHS(
268 const std::initializer_list<CH32USBOtgHS::EPConfig> EP_CFGS, uint16_t vid,
269 uint16_t pid, uint16_t bcd,
270 const std::initializer_list<const USB::DescriptorStrings::LanguagePack*> LANG_LIST,
271 const std::initializer_list<const std::initializer_list<USB::ConfigDescriptorItem*>>
272 CONFIGS,
273 ConstRawData uid)
274 : USB::EndpointPool(EP_CFGS.size() * 2),
275 USB::DeviceCore(*this, USB::USBSpec::USB_2_1, USB::Speed::HIGH,
276 USB::DeviceDescriptor::PacketSize0::SIZE_64, vid, pid, bcd,
277 LANG_LIST, CONFIGS, uid)
278{
279 self_ = this;
280 ASSERT(EP_CFGS.size() > 0 && EP_CFGS.size() <= CH32EndpointOtgHs::EP_OTG_HS_MAX_SIZE);
281
282 auto cfgs_itr = EP_CFGS.begin();
283
284 ASSERT(cfgs_itr->buffer_tx.size_ >= 64 && cfgs_itr->buffer_rx.size_ >= 64 &&
285 cfgs_itr->double_buffer == false);
286
287 auto ep0_out =
288 new CH32EndpointOtgHs(USB::Endpoint::EPNumber::EP0, USB::Endpoint::Direction::OUT,
289 cfgs_itr->buffer_rx, false);
290 auto ep0_in =
291 new CH32EndpointOtgHs(USB::Endpoint::EPNumber::EP0, USB::Endpoint::Direction::IN,
292 cfgs_itr->buffer_tx, false);
293
294 USB::EndpointPool::SetEndpoint0(ep0_in, ep0_out);
295
296 USB::Endpoint::EPNumber ep_index = USB::Endpoint::EPNumber::EP1;
297
298 for (++cfgs_itr, ep_index = USB::Endpoint::EPNumber::EP1; cfgs_itr != EP_CFGS.end();
299 ++cfgs_itr, ep_index = USB::Endpoint::NextEPNumber(ep_index))
300 {
301 if (!cfgs_itr->double_buffer)
302 {
303 auto ep_out = new CH32EndpointOtgHs(ep_index, USB::Endpoint::Direction::OUT,
304 cfgs_itr->buffer_rx, false);
305 USB::EndpointPool::Put(ep_out);
306
307 auto ep_in = new CH32EndpointOtgHs(ep_index, USB::Endpoint::Direction::IN,
308 cfgs_itr->buffer_tx, false);
309 USB::EndpointPool::Put(ep_in);
310 }
311 else
312 {
313 auto ep = new CH32EndpointOtgHs(
314 ep_index,
315 cfgs_itr->is_in ? USB::Endpoint::Direction::IN : USB::Endpoint::Direction::OUT,
316 cfgs_itr->buffer_tx, true);
317 USB::EndpointPool::Put(ep);
318 }
319 }
320}
321
322LibXR::ErrorCode CH32USBOtgHS::SetAddress(uint8_t address,
324{
325 if (context == USB::DeviceCore::Context::STATUS_IN_COMPLETE)
326 {
327 USBHSD->DEV_AD = address;
328 }
330}
331
332void CH32USBOtgHS::Start(bool)
333{
334 // OTGHS selects the shared 48 MHz source first, then enables its own bus clock.
335 // OTGHS 先选择共享 48 MHz 时钟源,再打开 USBHS 自己的总线时钟。
336 LibXR::CH32UsbRcc::ConfigureUsb48M();
337#if defined(RCC_AHBPeriph_USBHS)
338 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_USBHS, ENABLE);
339#endif
340 USBHSD->CONTROL = USBHS_UC_CLR_ALL | USBHS_UC_RESET_SIE;
341 USBHSD->CONTROL &= ~USBHS_UC_RESET_SIE;
342 USBHSD->HOST_CTRL = USBHS_UH_PHY_SUSPENDM;
343 USBHSD->CONTROL = USBHS_UC_DMA_EN | USBHS_UC_INT_BUSY | USBHS_UC_SPEED_HIGH;
344 USBHSD->INT_EN =
345 USBHS_UIE_SETUP_ACT | USBHS_UIE_TRANSFER | USBHS_UIE_DETECT | USBHS_UIE_SUSPEND;
346 USBHSD->CONTROL |= USBHS_UC_DEV_PU_EN;
347 NVIC_EnableIRQ(USBHS_IRQn);
348}
349
350void CH32USBOtgHS::Stop(bool)
351{
352 USBHSD->CONTROL = USBHS_UC_CLR_ALL | USBHS_UC_RESET_SIE;
353 USBHSD->CONTROL = 0;
354 NVIC_DisableIRQ(USBHS_IRQn);
355}
356
357#endif // defined(USBHSD)
358
359// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
常量原始数据封装类。 A class for encapsulating constant raw data.
USB 设备协议栈核心:EP0 控制传输、描述符、配置、标准/类/厂商请求 USB device core: EP0 control transfer, descriptors,...
Definition dev_core.hpp:22
Context
控制传输上下文 / Control transfer context
Definition dev_core.hpp:31
USB描述符基类 USB descriptor base class.
Definition desc_dev.hpp:40
EPNumber
端点号 Endpoint number
Definition ep.hpp:42
@ IN
输入方向 / IN direction
@ OUT
输出方向 / OUT direction
static constexpr EPNumber NextEPNumber(EPNumber ep)
获取下一个端点号 / Get the next endpoint number
Definition ep.hpp:128
USB端点池类 / USB endpoint pool class.
Definition ep_pool.hpp:23
void SetEndpoint0(Endpoint *ep0_in, Endpoint *ep0_out)
设置端点0的IN/OUT对象 / Set Endpoint 0 IN/OUT objects
Definition ep_pool.cpp:96
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ OK
操作成功 | Operation successful
USB 标准请求 SETUP 包(固定8字节) Standard USB setup packet (8 bytes)
Definition core.hpp:57