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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
|
/***************************************************************************
* Copyright (c) 2024 Microsoft Corporation
* Copyright (c) 2026-present Eclipse ThreadX contributors
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* SPDX-License-Identifier: MIT
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** USBX Component */
/** */
/** Host Simulator Controller Driver */
/** */
/**************************************************************************/
/**************************************************************************/
#define UX_SOURCE_CODE
/* Include necessary system files. */
#include "ux_api.h"
#include "ux_hcd_sim_host.h"
#include "ux_dcd_sim_slave.h"
#include "ux_device_stack.h"
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _ux_hcd_sim_host_transaction_schedule PORTABLE C */
/* 6.1.12 */
/* AUTHOR */
/* */
/* Chaoqiong Xiao, Microsoft Corporation */
/* */
/* DESCRIPTION */
/* */
/* This function bridges a transaction from the host to the slave */
/* simulation controller. */
/* */
/* INPUT */
/* */
/* hcd_sim_host Pointer to host controller */
/* ed Pointer to ED */
/* */
/* OUTPUT */
/* */
/* Completion Status */
/* */
/* CALLS */
/* */
/* (ux_transfer_request_completion_function) */
/* Completion function */
/* _ux_device_stack_control_request_process */
/* Process request */
/* _ux_utility_memory_copy Copy memory block */
/* _ux_utility_semaphore_put Semaphore put */
/* */
/* CALLED BY */
/* */
/* Host Simulator Controller Driver */
/* */
/**************************************************************************/
UINT _ux_hcd_sim_host_transaction_schedule(UX_HCD_SIM_HOST *hcd_sim_host, UX_HCD_SIM_HOST_ED *ed)
{
UX_DCD_SIM_SLAVE *dcd_sim_slave;
UX_HCD_SIM_HOST_TD *td;
UX_HCD_SIM_HOST_TD *head_td;
UX_HCD_SIM_HOST_TD *tail_td;
UX_HCD_SIM_HOST_TD *data_td;
UX_ENDPOINT *endpoint;
UX_SLAVE_ENDPOINT *slave_endpoint;
UX_DCD_SIM_SLAVE_ED *slave_ed;
ULONG slave_transfer_remaining;
UCHAR wake_host;
UCHAR wake_slave;
ULONG transaction_length;
ULONG td_length;
UX_SLAVE_TRANSFER *slave_transfer_request;
UX_TRANSFER *transfer_request;
ULONG endpoint_index;
UX_SLAVE_DCD *dcd;
UX_PARAMETER_NOT_USED(hcd_sim_host);
/* Get the pointer to the DCD portion of the simulator. */
dcd = &_ux_system_slave -> ux_system_slave_dcd;
/* Check the state of the controller if OPERATIONAL . */
if (dcd -> ux_slave_dcd_status != UX_DCD_STATUS_OPERATIONAL)
return(UX_ERROR);
/* Get the pointer to the candidate TD on the host. */
td = ed -> ux_sim_host_ed_head_td;
/* Get the pointer to the endpoint. */
endpoint = ed -> ux_sim_host_ed_endpoint;
/* Get the pointer to the transfer_request attached with this TD. */
transfer_request = td -> ux_sim_host_td_transfer_request;
/* Get the index of the endpoint from the host. */
endpoint_index = endpoint -> ux_endpoint_descriptor.bEndpointAddress & ~(ULONG)UX_ENDPOINT_DIRECTION;
/* Get the address of the device controller. */
dcd_sim_slave = (UX_DCD_SIM_SLAVE *) dcd -> ux_slave_dcd_controller_hardware;
/* Get the endpoint as seen from the device side. */
#ifdef UX_DEVICE_BIDIRECTIONAL_ENDPOINT_SUPPORT
slave_ed = ((endpoint -> ux_endpoint_descriptor.bEndpointAddress == 0) ?
&dcd_sim_slave -> ux_dcd_sim_slave_ed[0] :
((endpoint -> ux_endpoint_descriptor.bEndpointAddress & UX_ENDPOINT_DIRECTION) ?
&dcd_sim_slave -> ux_dcd_sim_slave_ed_in[endpoint_index] :
&dcd_sim_slave -> ux_dcd_sim_slave_ed[endpoint_index]));
#else
slave_ed = &dcd_sim_slave -> ux_dcd_sim_slave_ed[endpoint_index];
#endif
/* Is this ED used? */
if ((slave_ed -> ux_sim_slave_ed_status & UX_DCD_SIM_SLAVE_ED_STATUS_USED) == 0)
return(UX_ERROR);
/* Is this ED ready for transaction or stalled ? */
if ((slave_ed -> ux_sim_slave_ed_status & (UX_DCD_SIM_SLAVE_ED_STATUS_TRANSFER | UX_DCD_SIM_SLAVE_ED_STATUS_STALLED)) == 0)
return(UX_ERROR);
/* Get the logical endpoint from the physical endpoint. */
slave_endpoint = slave_ed -> ux_sim_slave_ed_endpoint;
/* Get the pointer to the transfer request. */
slave_transfer_request = &slave_endpoint -> ux_slave_endpoint_transfer_request;
/* Check the phase for this transfer, if this is the SETUP phase, treatment is different. Explanation of how
control transfers are handled in the simulator: if the data phase is OUT, we handle it immediately, meaning we
send all the data to the device and remove the STATUS TD in the same scheduler call. If the data phase is IN, we
only take out the SETUP TD and handle the data phase like any other non-control transactions (i.e. the scheduler
calls us again with the DATA TDs). */
if (td -> ux_sim_host_td_status & UX_HCD_SIM_HOST_TD_SETUP_PHASE)
{
/* For control transfer, stall is for protocol error and it's cleared any time when SETUP is received */
slave_ed -> ux_sim_slave_ed_status &= ~(ULONG)UX_DCD_SIM_SLAVE_ED_STATUS_STALLED;
/* Validate the length to the setup transaction buffer. */
UX_ASSERT(td -> ux_sim_host_td_length == 8);
/* Reset actual data length (not including SETUP received) so far. */
slave_transfer_request -> ux_slave_transfer_request_actual_length = 0;
/* Move the buffer from the host TD to the device TD. */
_ux_utility_memory_copy(slave_transfer_request -> ux_slave_transfer_request_setup,
td -> ux_sim_host_td_buffer,
td -> ux_sim_host_td_length); /* Use case of memcpy is verified. */
#if defined(UX_HOST_STANDALONE)
/* The setup buffer is allocated, release it since it's used. */
_ux_utility_memory_free(td -> ux_sim_host_td_buffer);
td -> ux_sim_host_td_buffer = UX_NULL;
#endif
/* The setup phase never fails. We acknowledge the transfer code here by taking the TD out of the endpoint. */
ed -> ux_sim_host_ed_head_td = td -> ux_sim_host_td_next_td;
/* Free the TD that was used here. */
td -> ux_sim_host_td_status = UX_UNUSED;
/* Check if the transaction is OUT from the host and there is data payload. */
if (((*slave_transfer_request -> ux_slave_transfer_request_setup & UX_REQUEST_IN) == 0) &&
(*(slave_transfer_request -> ux_slave_transfer_request_setup + 6) != 0 ||
*(slave_transfer_request -> ux_slave_transfer_request_setup + 7) != 0))
{
/* This is the case where there is a data payload OUT from host to device.
the data needs to be copied into the device buffer first before invoking the control
dispatcher. */
/* Get the length we expect from the SETUP packet (target the entire available control buffer). */
slave_transfer_request -> ux_slave_transfer_request_requested_length = _ux_utility_short_get(slave_transfer_request -> ux_slave_transfer_request_setup + 6);
/* Avoid buffer overflow. */
if (slave_transfer_request -> ux_slave_transfer_request_requested_length > UX_SLAVE_REQUEST_CONTROL_MAX_LENGTH)
{
/* Error trap. */
_ux_system_error_handler(UX_SYSTEM_LEVEL_THREAD, UX_SYSTEM_CONTEXT_DCD, UX_TRANSFER_BUFFER_OVERFLOW);
slave_transfer_request -> ux_slave_transfer_request_requested_length = UX_SLAVE_REQUEST_CONTROL_MAX_LENGTH;
}
/* And reprogram the current buffer address to the beginning of the buffer. */
slave_transfer_request -> ux_slave_transfer_request_current_data_pointer = slave_transfer_request -> ux_slave_transfer_request_data_pointer;
/* Get the pointer to the first data TD. this is the TD right after the SETUP one. */
data_td = td -> ux_sim_host_td_next_td;
/* Get the data length we expect. */
transaction_length = slave_transfer_request -> ux_slave_transfer_request_requested_length;
/* It may have taken multiple TDs to send all the data. */
while (transaction_length != 0)
{
/* Do a sanity check. TD must be data. */
if (data_td -> ux_sim_host_td_status & UX_HCD_SIM_HOST_TD_DATA_PHASE)
{
/* Copy the amount of data in the td into the slave transaction buffer. */
td_length = UX_MIN(data_td -> ux_sim_host_td_length, transaction_length);
_ux_utility_memory_copy(slave_transfer_request -> ux_slave_transfer_request_current_data_pointer,
data_td -> ux_sim_host_td_buffer,
td_length); /* Use case of memcpy is verified. */
/* Add to the actual payload length. */
slave_transfer_request -> ux_slave_transfer_request_actual_length += td_length;
/* Update the host transfer's actual length. */
transfer_request -> ux_transfer_request_actual_length += transaction_length;
/* Decrement the total length. */
transaction_length -= td_length;
/* Update buffer pointer. */
slave_transfer_request -> ux_slave_transfer_request_current_data_pointer += td_length;
/* Update the td in the head. */
ed -> ux_sim_host_ed_head_td = data_td;
/* Get the pointer to the next data TD. */
data_td = data_td -> ux_sim_host_td_next_td;
/* Free the TD that was used here. */
ed -> ux_sim_host_ed_head_td -> ux_sim_host_td_status = UX_UNUSED;
}
}
/* Make the head TD point to the STATUS TD. */
ed -> ux_sim_host_ed_head_td = ed -> ux_sim_host_ed_head_td -> ux_sim_host_td_next_td;
}
/* Is there no hub? */
if (dcd_sim_slave -> ux_dcd_sim_slave_dcd_control_request_process_hub == UX_NULL)
{
/* There's no hub to worry about. This control transfer is for the
device itself. */
/* Pass the transfer to the regular device stack. */
_ux_device_stack_control_request_process(slave_transfer_request);
}
else
{
/* There is a hub. We need to call the correct Control Transfer dispatcher.
If the device is a hub and this transfer is for one of the devices on the
hub, then we must invoke a separate Control Transfer dispatcher besides
the regular device stack's. This is because the current device stack doesn't
handle control transfers to device's other than itself. */
/* Is this meant for the device itself? */
if (/* If the device isn't ADDRESSED yet, then the address may be invalid since we don't
clear it upon disconnection/reconnection. So we assume that if the device is RESET
or ATTACHED, the control transfer is meant for the device itself. */
(_ux_system_slave->ux_system_slave_device.ux_slave_device_state == UX_DEVICE_RESET ||
_ux_system_slave->ux_system_slave_device.ux_slave_device_state == UX_DEVICE_ATTACHED) ||
/* If we get to this check, then the device has been ADDRESSED and we can compare addresses. */
endpoint -> ux_endpoint_device -> ux_device_address == dcd -> ux_slave_dcd_device_address)
{
/* Yes, this control transfer is meant for the device itself. */
/* Pass the transfer to the regular device stack. */
_ux_device_stack_control_request_process(slave_transfer_request);
}
else
{
/* No, this control transfer is meant for a device on the hub. */
/* Pass the transfer to the callback. */
dcd_sim_slave -> ux_dcd_sim_slave_dcd_control_request_process_hub(slave_transfer_request);
}
}
/* Check if the transaction is OUT from the host. */
if ((*slave_transfer_request -> ux_slave_transfer_request_setup & UX_REQUEST_IN) == 0)
{
/* Check if there is a problem with the endpoint (maybe stalled). */
if (slave_ed -> ux_sim_slave_ed_status & UX_DCD_SIM_SLAVE_ED_STATUS_STALLED)
{
/* Protocol error, stall the transaction. */
transfer_request -> ux_transfer_request_completion_code = UX_TRANSFER_STALLED;
if (transfer_request -> ux_transfer_request_completion_function != UX_NULL)
transfer_request -> ux_transfer_request_completion_function(transfer_request);
/* Error trap. */
_ux_system_error_handler(UX_SYSTEM_LEVEL_THREAD, UX_SYSTEM_CONTEXT_HCD, UX_TRANSFER_STALLED);
/* If trace is enabled, insert this event into the trace buffer. */
UX_TRACE_IN_LINE_INSERT(UX_TRACE_ERROR, UX_TRANSFER_STALLED, transfer_request, 0, 0, UX_TRACE_ERRORS, 0, 0)
}
else
/* No error in simulation. */
transfer_request -> ux_transfer_request_completion_code = UX_SUCCESS;
/* In this case the transfer is completed! We take out the status TD. */
td = ed -> ux_sim_host_ed_head_td;
/* Adjust the ED. */
ed -> ux_sim_host_ed_head_td = td -> ux_sim_host_td_next_td;
/* Free the TD that was used here. */
td -> ux_sim_host_td_status = UX_UNUSED;
/* Then, we wake up the host. */
_ux_host_semaphore_put(&transfer_request -> ux_transfer_request_semaphore);
}
}
else
{
/* Check if there is a problem with the endpoint (maybe stalled). */
if (slave_ed -> ux_sim_slave_ed_status & UX_DCD_SIM_SLAVE_ED_STATUS_STALLED)
{
/* Stall the transaction. */
transfer_request -> ux_transfer_request_completion_code = UX_TRANSFER_STALLED;
if (transfer_request -> ux_transfer_request_completion_function != UX_NULL)
transfer_request -> ux_transfer_request_completion_function(transfer_request);
/* Error trap. */
_ux_system_error_handler(UX_SYSTEM_LEVEL_THREAD, UX_SYSTEM_CONTEXT_HCD, UX_TRANSFER_STALLED);
/* If trace is enabled, insert this event into the trace buffer. */
UX_TRACE_IN_LINE_INSERT(UX_TRACE_ERROR, UX_TRANSFER_STALLED, transfer_request, 0, 0, UX_TRACE_ERRORS, 0, 0)
/* Wake up the host side. */
_ux_host_semaphore_put(&transfer_request -> ux_transfer_request_semaphore);
/* Clean up this ED. */
head_td = ed -> ux_sim_host_ed_head_td;
tail_td = ed -> ux_sim_host_ed_tail_td;
/* Free all TDs attached to the ED. */
while (head_td != tail_td)
{
/* Mark the current head TD as free. */
head_td -> ux_sim_host_td_status = UX_UNUSED;
/* Update the head TD with the next TD. */
ed -> ux_sim_host_ed_head_td = head_td -> ux_sim_host_td_next_td;
/* Now the new head_td is the next TD in the chain. */
head_td = ed -> ux_sim_host_ed_head_td;
}
}
else
{
slave_transfer_remaining = 0;
/* If the device tries to send a NULL packet, we don't reset the actual length to 0. */
if (slave_transfer_request -> ux_slave_transfer_request_requested_length != 0)
slave_transfer_remaining = slave_transfer_request -> ux_slave_transfer_request_requested_length - slave_transfer_request -> ux_slave_transfer_request_actual_length;
/* Get the transaction length to be transferred. It could be a ZLP condition. */
if (slave_transfer_remaining <= td -> ux_sim_host_td_length)
transaction_length = slave_transfer_remaining;
else
transaction_length = td -> ux_sim_host_td_length;
if (transaction_length)
{
if (td -> ux_sim_host_td_direction == UX_HCD_SIM_HOST_TD_OUT)
/* Send the requested host data to the device. */
_ux_utility_memory_copy(slave_transfer_request -> ux_slave_transfer_request_current_data_pointer,
td -> ux_sim_host_td_buffer,
transaction_length); /* Use case of memcpy is verified. */
else
/* Send the requested host data to the device. */
_ux_utility_memory_copy(td -> ux_sim_host_td_buffer,
slave_transfer_request -> ux_slave_transfer_request_current_data_pointer,
transaction_length); /* Use case of memcpy is verified. */
}
/* Update buffers. */
td -> ux_sim_host_td_buffer += transaction_length;
slave_transfer_request -> ux_slave_transfer_request_current_data_pointer += transaction_length;
/* Update actual length values. */
td -> ux_sim_host_td_actual_length += transaction_length;
transfer_request -> ux_transfer_request_actual_length += transaction_length;
slave_transfer_request -> ux_slave_transfer_request_actual_length += transaction_length;
/* Update requested length values. */
td -> ux_sim_host_td_length -= transaction_length;
/* Are we done with this TD (It's possible for the TD to expect more data; for example, the slave
sent/received a smaller amount)? */
if (td -> ux_sim_host_td_length == 0)
{
/* Free the TD that was used here. */
td -> ux_sim_host_td_status = UX_UNUSED;
/* Adjust the ED. */
ed -> ux_sim_host_ed_head_td = td -> ux_sim_host_td_next_td;
}
/* Reset wake booleans. */
wake_host = UX_FALSE;
wake_slave = UX_FALSE;
/* Does the slave have absolutely no more data to send? */
if (slave_endpoint -> ux_slave_endpoint_descriptor.wMaxPacketSize == 0) /* Special for tests (avoid DIV0/MOD0) */
{
/* This happens only if device descriptor bMaxPacketSize0 is zero, assume it's OK for the first control
requests to let host check the descriptor.
If wMaxPacketSize is zero, host reject the device and transfer never started to get here. */
wake_host = UX_TRUE;
wake_slave = UX_TRUE;
}
else if ((transaction_length == 0) ||
(transaction_length % slave_endpoint -> ux_slave_endpoint_descriptor.wMaxPacketSize))
{
/* Host got ZLP or short packet. */
wake_host = UX_TRUE;
wake_slave = UX_TRUE;
}
else
{
/* Is the host's transfer completed? */
if (transfer_request -> ux_transfer_request_actual_length == transfer_request -> ux_transfer_request_requested_length)
wake_host = UX_TRUE;
/* Is the slaves's transfer completed? */
if (slave_transfer_request -> ux_slave_transfer_request_actual_length ==
slave_transfer_request -> ux_slave_transfer_request_requested_length)
{
if (slave_transfer_request -> ux_slave_transfer_request_requested_length == 0 ||
slave_transfer_request -> ux_slave_transfer_request_force_zlp == 0)
wake_slave = UX_TRUE;
else
slave_transfer_request -> ux_slave_transfer_request_force_zlp = 0;
}
}
if (wake_slave == UX_TRUE)
{
/* Set the completion code to no error. */
slave_transfer_request -> ux_slave_transfer_request_completion_code = UX_SUCCESS;
/* Set the transfer status to COMPLETED. */
slave_transfer_request -> ux_slave_transfer_request_status = UX_TRANSFER_STATUS_COMPLETED;
/* Is this not the control endpoint? */
if (slave_ed -> ux_sim_slave_ed_index != 0)
{
/* Clear pending flag. */
slave_ed -> ux_sim_slave_ed_status &= ~(ULONG)UX_DCD_SIM_SLAVE_ED_STATUS_TRANSFER;
/* Set done flag. */
slave_ed -> ux_sim_slave_ed_status |= UX_DCD_SIM_SLAVE_ED_STATUS_DONE;
/* Wake up the slave side. */
_ux_device_semaphore_put(&slave_transfer_request -> ux_slave_transfer_request_semaphore);
}
}
if (wake_host == UX_TRUE)
{
/* If the slave has less data to send than the host wants to receive, then there may still be
TDs left to free. Note that this should only happen for IN transactions, since the only way
an OUT transfer can complete is if all the data was sent i.e. all the TDs were sent and freed. */
if (ed -> ux_sim_host_ed_head_td != ed -> ux_sim_host_ed_tail_td)
{
/* Free all TDs associated with this transfer. Note that if this is a control transfer (which
means it must be IN), then this also gets rid of the STATUS phase, which is okay. Also note
that assumes that there is only one transfer occurring on this endpoint; if there were
multiple, then we'd erase that one's TDs as well. Luckily, with the way USBX is designed,
there should never be multiple transfers occurring simultaneously on a single endpoint
(tldr; data pointer for transfers is shared). */
head_td = ed -> ux_sim_host_ed_head_td;
while (head_td != ed -> ux_sim_host_ed_tail_td)
{
/* Free the TD that was used here. */
head_td -> ux_sim_host_td_status = UX_UNUSED;
/* Move to the next. */
head_td = head_td -> ux_sim_host_td_next_td;
}
/* Update the head and tail TD. */
ed -> ux_sim_host_ed_head_td = head_td;
ed -> ux_sim_host_ed_tail_td = head_td;
}
/* Set the completion code to no error. */
transfer_request -> ux_transfer_request_completion_code = UX_SUCCESS;
/* Set the transfer status to COMPLETED. */
transfer_request -> ux_transfer_request_status = UX_TRANSFER_STATUS_COMPLETED;
/* Is there a callback on the host? */
if (transfer_request -> ux_transfer_request_completion_function != UX_NULL)
transfer_request -> ux_transfer_request_completion_function(transfer_request);
/* Wake up the host side. */
_ux_host_semaphore_put(&transfer_request -> ux_transfer_request_semaphore);
}
}
}
/* Return successful completion. */
return(UX_SUCCESS);
}
|