ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ux_dcd_ra8_usb_xfer.c
Go to the documentation of this file.
1
21
22#define UX_SOURCE_CODE
23
24#include <stdint.h>
25#include <string.h>
26
27#include "ra8_check.h"
28#include "ra8_elc_regs.h"
29#include "ra8_isr.h"
30#include "ra8_log.h"
31#include "ra8_usb_regs.h"
32#include "tx_api.h"
33#include "ux_api.h"
34#include "ux_dcd_ra8_usb.h"
36#include "ux_device_stack.h"
37#include "ux_system.h"
38#include "ux_utility.h"
39
41
42uint16_t g_orphan_len = 0U;
43
44uint8_t g_orphan_pipe = 0U;
45
46/* -------------------------------------------------------------------------- */
47/* Internal helpers */
48/* -------------------------------------------------------------------------- */
49
66uint8_t priv_ep_to_pipe(uint8_t ep_addr)
67{
68 const uint8_t ep = ep_addr & (uint8_t)k_ra8_usb_ep_addr_num_mask;
69 if (ep == 0U) {
70 return 0U;
71 }
72 if (ep < (uint8_t)k_ux_dcd_ra8_usb_max_pipes) {
73 return ep;
74 }
75 return (uint8_t)k_ux_dcd_ra8_usb_max_pipes;
76}
77
105RA8_INTERNAL static unsigned int internal_ep0_transfer(struct UX_SLAVE_TRANSFER_STRUCT* tr)
106{
107 if (tr->ux_slave_transfer_request_in_transfer_length != 0U &&
108 tr->ux_slave_transfer_request_data_pointer != nullptr) {
109 /* USBX only routes the EP0 *IN* data stage (descriptors, class GET_*)
110 * through the DCD transfer function via ``in_transfer_length``. The
111 * host->device data stage (e.g. DFU_DNLOAD) is delivered into the
112 * control buffer BEFORE the class entry runs -- see the deferred
113 * ::ra8_usb_dcp_out_arm / ::ra8_usb_dcp_out_read path driven from
114 * ::priv_dispatch_setup and ::priv_handle_ctrl_out_data. */
115 const uint16_t len = (uint16_t)tr->ux_slave_transfer_request_in_transfer_length;
116 if (ra8_usb_dcp_in_data(g_dcd.speed, tr->ux_slave_transfer_request_data_pointer, len) !=
117 k_ra8_ok) {
118 return UX_TRANSFER_ERROR;
119 }
120 tr->ux_slave_transfer_request_actual_length = len;
121 return UX_SUCCESS;
122 }
123 if (ra8_usb_control_response(g_dcd.speed, true) != k_ra8_ok) {
124 return UX_TRANSFER_ERROR;
125 }
126 return UX_SUCCESS;
127}
128
156#ifndef UX_DEVICE_STANDALONE
181RA8_INTERNAL static unsigned int internal_wait_completion(struct UX_SLAVE_TRANSFER_STRUCT* tr,
182 uint8_t pipe)
183{
184 ULONG timeout = tr->ux_slave_transfer_request_timeout;
185 if (timeout == 0U) {
186 timeout = TX_WAIT_FOREVER;
187 }
188 if (pipe == 2U) {
189 g_diag.xfer_req_pipe2_block++;
190 }
191 UINT sem_status = tx_semaphore_get(&tr->ux_slave_transfer_request_semaphore, timeout);
192 if (pipe == 2U) {
193 g_diag.xfer_req_pipe2_woken++;
194 }
195 if (sem_status != TX_SUCCESS) {
196 /* Timeout / aborted: drop the pipe slot so a stale stash cannot
197 * cause the IRQ path to complete a now-defunct request. */
198 g_dcd.pipes[pipe].xfer = nullptr;
199 tr->ux_slave_transfer_request_completion_code = UX_TRANSFER_ERROR;
200 return UX_TRANSFER_ERROR;
201 }
202 return tr->ux_slave_transfer_request_completion_code;
203}
204
205#endif
206
234RA8_INTERNAL static unsigned int internal_submit_consume_orphan(struct UX_SLAVE_TRANSFER_STRUCT* tr,
235 uint8_t pipe)
236{
237 const uint16_t req = (uint16_t)tr->ux_slave_transfer_request_requested_length;
238 const uint16_t mps = g_dcd.pipes[pipe].max_pkt;
239 const uint16_t held = g_orphan_len;
240 const uint16_t n = (held < req) ? held : req;
242 (void)memcpy(tr->ux_slave_transfer_request_data_pointer, g_orphan_buf, (size_t)n);
243 g_orphan_len = 0U;
244 tr->ux_slave_transfer_request_actual_length = n;
245
246 bool done = (n >= req);
247 if (held < mps) {
248 done = true; /* short held packet terminates the OUT data phase */
249 }
250 if (done) {
251 tr->ux_slave_transfer_request_completion_code = UX_SUCCESS;
252 g_dcd.pipes[pipe].xfer = nullptr;
253#ifndef UX_DEVICE_STANDALONE
254 (void)tx_semaphore_put(&tr->ux_slave_transfer_request_semaphore);
255#endif
256 return UX_SUCCESS;
257 }
258 /* Partial: more packets expected -- arm BUF for the streaming tail. */
259 if (ra8_usb_rearm_out_pipe(g_dcd.speed, pipe) != k_ra8_ok) {
260 g_dcd.pipes[pipe].xfer = nullptr;
261 return UX_TRANSFER_ERROR;
262 }
263 return UX_SUCCESS;
264}
265
289RA8_INTERNAL static uint32_t internal_fifo_lock(void)
290{
291 uint32_t primask = 0U;
292 __asm__ volatile("mrs %0, primask" : "=r"(primask));
293 __asm__ volatile("cpsid i" ::: "memory");
294 return primask;
295}
296
314RA8_INTERNAL static void internal_fifo_unlock(uint32_t primask)
315{
316 if ((primask & 1U) == 0U) {
317 __asm__ volatile("cpsie i" ::: "memory");
318 }
319}
320
348RA8_INTERNAL static unsigned int internal_submit_in_pipe(struct UX_SLAVE_TRANSFER_STRUCT* tr,
349 uint8_t pipe)
350{
351 const uint16_t total = (uint16_t)tr->ux_slave_transfer_request_requested_length;
352 const uint16_t mps = g_dcd.pipes[pipe].max_pkt;
353 const uint16_t chunk = (total > mps) ? mps : total;
354 /* The ISR walk shares the CFIFO port; stage atomically (see
355 * internal_fifo_lock). */
356 const uint32_t primask = internal_fifo_lock();
357 if (ra8_usb_queue_in(g_dcd.speed, pipe, tr->ux_slave_transfer_request_data_pointer, chunk) !=
358 k_ra8_ok) {
359 internal_fifo_unlock(primask);
360 g_dcd.pipes[pipe].xfer = nullptr;
361 return UX_TRANSFER_ERROR;
362 }
363 tr->ux_slave_transfer_request_actual_length = chunk;
364 /* Trailing ZLP policy: honor USBX's per-transfer force_zlp flag
365 * (set by ux_device_stack_transfer_request only when the host asked
366 * for MORE than the payload and the payload is MPS-aligned). An
367 * unconditional MPS-exact ZLP corrupts BOT: a 512-byte READ(10)
368 * data phase on a 512-MPS HS bulk pipe is single-packet MPS-exact,
369 * and the spurious ZLP lands where the host expects the CSW
370 * (observed against macOS at HS as a read-LBA0-then-reset loop).
371 * The ZLP itself is staged by internal_irq_complete_in on the data
372 * packet's BEMP -- bulk IN pipes run single-banked, so there is no
373 * second bank to pre-stage it into here. */
374 internal_fifo_unlock(primask);
375 return UX_SUCCESS;
376}
377
408RA8_INTERNAL static unsigned int
409internal_submit_pipe(struct UX_SLAVE_TRANSFER_STRUCT* tr, uint8_t pipe, uint8_t ep_addr)
410{
411 if ((ep_addr & (uint8_t)k_ra8_usb_ep_addr_dir_in_bit) != 0U) {
412 return internal_submit_in_pipe(tr, pipe);
413 }
414 /* OUT pipe. A host packet may already have landed in the controller
415 * bank during the PID=BUF window after the previous transfer (the
416 * host pipelines a CBW's data phase). internal_irq_drain_orphan_out
417 * stashes such a packet; the bulk-OUT wire is serial so it belongs to
418 * THIS transfer -- deliver it as packet one. Nested ifs keep the test
419 * out of the MC/DC inventory. */
420 if (g_orphan_len != 0U) {
421 if (g_orphan_pipe == pipe) {
422 return internal_submit_consume_orphan(tr, pipe);
423 }
424 }
425 /* No held packet: move PID from NAK to BUF so the controller ACKs the
426 * host's first OUT token. internal_irq_complete_out parks the pipe
427 * back at NAK once the transfer drains. HUM Ch 36.2.27 PIPECTR.PID. */
428 if (ra8_usb_rearm_out_pipe(g_dcd.speed, pipe) != k_ra8_ok) {
429 g_dcd.pipes[pipe].xfer = nullptr;
430 return UX_TRANSFER_ERROR;
431 }
432 return UX_SUCCESS;
433}
434
465unsigned int priv_transfer_request(struct UX_SLAVE_TRANSFER_STRUCT* tr)
466{
467 g_diag.xfer_req_total++;
468 if (tr == nullptr) {
469 g_diag.xfer_req_null_arg++;
470 return UX_TRANSFER_ERROR;
471 }
472 if (tr->ux_slave_transfer_request_endpoint == nullptr) {
473 g_diag.xfer_req_null_arg++;
474 return UX_TRANSFER_ERROR;
475 }
476
477 UX_SLAVE_ENDPOINT* ep = tr->ux_slave_transfer_request_endpoint;
478 const uint8_t ep_addr = (uint8_t)ep->ux_slave_endpoint_descriptor.bEndpointAddress;
479 const uint8_t pipe = priv_ep_to_pipe(ep_addr);
480 if (pipe >= (uint8_t)k_ux_dcd_ra8_usb_max_pipes) {
481 g_diag.xfer_req_bad_pipe++;
482 return UX_TRANSFER_ERROR;
483 }
484 if (pipe == 2U) {
485 g_diag.xfer_req_pipe2_in++;
486 if ((ep_addr & (uint8_t)k_ra8_usb_ep_addr_dir_in_bit) == 0U) {
487 g_diag.xfer_req_pipe2_out_dir++;
488 }
489 }
490
491 if (pipe == 0U) {
492 return internal_ep0_transfer(tr);
493 }
494
495 /* Stash the active transfer so the IRQ path can post completion. */
496 g_dcd.pipes[pipe].xfer = tr;
497 g_dcd.pipes[pipe].ep_addr = ep_addr;
498 g_dcd.pipes[pipe].dir_in =
499 (uint8_t)((ep_addr & (uint8_t)k_ra8_usb_ep_addr_dir_in_bit) != 0U ? 1U : 0U);
500 g_dcd.pipes[pipe].max_pkt = (uint16_t)ep->ux_slave_endpoint_descriptor.wMaxPacketSize;
501 if (pipe == 2U) {
502 g_diag.xfer_req_pipe2_stashed++;
503 }
504
505 const unsigned int submit = internal_submit_pipe(tr, pipe, ep_addr);
506 if (submit != UX_SUCCESS) {
507 return submit;
508 }
509
510#ifndef UX_DEVICE_STANDALONE
511 return internal_wait_completion(tr, pipe);
512#else
513 return UX_SUCCESS;
514#endif
515}
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
Event Link Controller (ELC) register layout for the Renesas RA8D2.
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
NVIC + ICU IELSR allocator.
Lightweight Logging Interface for ra8-firmware.
ra8_err_t ra8_usb_control_response(ra8_usb_speed_t speed, bool accept)
Issue a control-transfer status response on EP0.
ra8_err_t ra8_usb_rearm_out_pipe(ra8_usb_speed_t speed, uint8_t pipe_num)
Re-arm an OUT pipe that the controller has parked at PID=NAK.
ra8_err_t ra8_usb_queue_in(ra8_usb_speed_t speed, uint8_t pipe_num, const uint8_t *data, uint16_t len)
Queue an IN transfer (device -> host) on pipe_num.
ra8_err_t ra8_usb_dcp_in_data(ra8_usb_speed_t speed, const uint8_t *data, uint16_t len)
Push the EP0 / DCP IN data-stage payload for a control transfer.
USB Full-Speed + High-Speed controller layout for the Renesas RA8D2.
unsigned int UINT
ThreadX-compatible unsigned int (host stub).
#define TX_SUCCESS
#define TX_WAIT_FOREVER
unsigned long ULONG
ThreadX-compatible unsigned long (host stub).
USBX device-controller-driver (DCD) bridge to ra8_usb.
@ k_ux_dcd_ra8_usb_max_pipes
DCP + 9 PIPE entries (HUM 36.1).
USBX device-controller-driver bridge to ra8_usb – per-module cross-translation-unit contract.
void priv_trace_event(uint8_t kind, uint8_t code, uint16_t length)
Append one packed event to the JLink-readable trace ring.
uint16_t g_orphan_len
Held orphan byte count; 0 = empty.
ra8_usb_dcd_t g_dcd
The single bridge instance, defined in ux_dcd_ra8_usb.c.
@ k_dcd_trace_kind_ouse
Orphan packet fed to a transfer.
ra8_usb_dcd_diag_t g_diag
Bridge diagnostic counter block, defined in ux_dcd_ra8_usb.c.
@ k_ra8_usb_ep_addr_dir_in_bit
Direction bit set => IN endpoint.
@ k_ra8_usb_ep_addr_num_mask
Endpoint-number field (bits 3:0).
uint8_t g_orphan_buf[k_ra8_usb_orphan_bytes]
One held no-receiver bulk-OUT packet.
uint8_t g_orphan_pipe
Pipe the held orphan packet is on.
@ k_ra8_usb_orphan_bytes
One full-speed bulk MPS packet.
static unsigned int internal_ep0_transfer(struct UX_SLAVE_TRANSFER_STRUCT *tr)
Drive an EP0 / DCP control transfer (data or status stage).
static uint32_t internal_fifo_lock(void)
Enter a CFIFO critical section: mask interrupts.
uint8_t priv_ep_to_pipe(uint8_t ep_addr)
Map a USB EP number (1..9) into our PIPE table index.
static unsigned int internal_submit_in_pipe(struct UX_SLAVE_TRANSFER_STRUCT *tr, uint8_t pipe)
Stage the first chunk of an IN transfer (plus single-packet ZLP).
static unsigned int internal_submit_pipe(struct UX_SLAVE_TRANSFER_STRUCT *tr, uint8_t pipe, uint8_t ep_addr)
Submit the stashed IN/OUT transfer on a non-EP0 pipe.
static unsigned int internal_wait_completion(struct UX_SLAVE_TRANSFER_STRUCT *tr, uint8_t pipe)
Block on the USBX transfer semaphore until the IRQ posts completion.
static unsigned int internal_submit_consume_orphan(struct UX_SLAVE_TRANSFER_STRUCT *tr, uint8_t pipe)
Deliver a held orphan OUT packet to a freshly submitted transfer.
static void internal_fifo_unlock(uint32_t primask)
Leave a CFIFO critical section: restore the interrupt mask.
unsigned int priv_transfer_request(struct UX_SLAVE_TRANSFER_STRUCT *tr)
USBX UX_DCD_TRANSFER_REQUEST dispatcher entry point.