]> git.proxmox.com Git - mirror_edk2.git/blame - MdeModulePkg/Bus/Pci/EhciPei/EhciUrb.c
MdeModulePkg/PiSmmCore: Implement heap guard feature for SMM mode
[mirror_edk2.git] / MdeModulePkg / Bus / Pci / EhciPei / EhciUrb.c
CommitLineData
4b1bf81c 1/** @file\r
2PEIM to produce gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid\r
3which is used to enable recovery function from USB Drivers.\r
4\r
5Copyright (c) 2010, Intel Corporation. All rights reserved.<BR>\r
6 \r
7This program and the accompanying materials\r
8are licensed and made available under the terms and conditions\r
9of the BSD License which accompanies this distribution. The\r
10full text of the license may be found at\r
11http://opensource.org/licenses/bsd-license.php\r
12\r
13THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
14WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
15\r
16**/\r
17\r
18#include "EhcPeim.h"\r
19\r
20/**\r
21 Delete a single asynchronous interrupt transfer for\r
22 the device and endpoint.\r
23 \r
24 @param Ehc The EHCI device.\r
25 @param Data Current data not associated with a QTD.\r
26 @param DataLen The length of the data.\r
27 @param PktId Packet ID to use in the QTD.\r
28 @param Toggle Data toggle to use in the QTD.\r
29 @param MaxPacket Maximu packet length of the endpoint.\r
30\r
31 @retval the pointer to the created QTD or NULL if failed to create one.\r
32\r
33**/\r
34PEI_EHC_QTD *\r
35EhcCreateQtd (\r
36 IN PEI_USB2_HC_DEV *Ehc,\r
37 IN UINT8 *Data,\r
38 IN UINTN DataLen,\r
39 IN UINT8 PktId,\r
40 IN UINT8 Toggle,\r
41 IN UINTN MaxPacket\r
42 )\r
43{\r
44 PEI_EHC_QTD *Qtd;\r
45 QTD_HW *QtdHw;\r
46 UINTN Index;\r
47 UINTN Len;\r
48 UINTN ThisBufLen;\r
49\r
50 ASSERT (Ehc != NULL);\r
51\r
52 Qtd = UsbHcAllocateMem (Ehc, Ehc->MemPool, sizeof (PEI_EHC_QTD));\r
53\r
54 if (Qtd == NULL) {\r
55 return NULL;\r
56 }\r
57\r
58 Qtd->Signature = EHC_QTD_SIG;\r
59 Qtd->Data = Data;\r
60 Qtd->DataLen = 0;\r
61\r
62 InitializeListHead (&Qtd->QtdList);\r
63\r
64 QtdHw = &Qtd->QtdHw;\r
65 QtdHw->NextQtd = QTD_LINK (NULL, TRUE);\r
66 QtdHw->AltNext = QTD_LINK (NULL, TRUE);\r
67 QtdHw->Status = QTD_STAT_ACTIVE;\r
68 QtdHw->Pid = PktId;\r
69 QtdHw->ErrCnt = QTD_MAX_ERR;\r
70 QtdHw->Ioc = 0;\r
71 QtdHw->TotalBytes = 0;\r
72 QtdHw->DataToggle = Toggle;\r
73\r
74 //\r
75 // Fill in the buffer points\r
76 //\r
77 if (Data != NULL) {\r
78 Len = 0;\r
79\r
80 for (Index = 0; Index <= QTD_MAX_BUFFER; Index++) {\r
81 //\r
82 // Set the buffer point (Check page 41 EHCI Spec 1.0). No need to\r
83 // compute the offset and clear Reserved fields. This is already\r
84 // done in the data point.\r
85 //\r
86 QtdHw->Page[Index] = EHC_LOW_32BIT (Data);\r
87 QtdHw->PageHigh[Index] = EHC_HIGH_32BIT (Data);\r
88\r
89 ThisBufLen = QTD_BUF_LEN - (EHC_LOW_32BIT (Data) & QTD_BUF_MASK);\r
90\r
91 if (Len + ThisBufLen >= DataLen) {\r
92 Len = DataLen;\r
93 break;\r
94 }\r
95\r
96 Len += ThisBufLen;\r
97 Data += ThisBufLen;\r
98 }\r
99 \r
100 //\r
101 // Need to fix the last pointer if the Qtd can't hold all the\r
102 // user's data to make sure that the length is in the unit of\r
103 // max packets. If it can hold all the data, there is no such\r
104 // need.\r
105 //\r
106 if (Len < DataLen) {\r
107 Len = Len - Len % MaxPacket;\r
108 }\r
109\r
110 QtdHw->TotalBytes = (UINT32) Len;\r
111 Qtd->DataLen = Len;\r
112 }\r
113\r
114 return Qtd;\r
115}\r
116\r
117/**\r
118 Initialize the queue head for interrupt transfer,\r
119 that is, initialize the following three fields:\r
120 1. SplitXState in the Status field.\r
121 2. Microframe S-mask.\r
122 3. Microframe C-mask.\r
123 \r
124 @param Ep The queue head's related endpoint.\r
125 @param QhHw The queue head to initialize.\r
126\r
127**/\r
128VOID\r
129EhcInitIntQh (\r
130 IN USB_ENDPOINT *Ep,\r
131 IN QH_HW *QhHw\r
132 )\r
133{\r
134 //\r
135 // Because UEFI interface can't utilitize an endpoint with\r
136 // poll rate faster than 1ms, only need to set one bit in\r
137 // the queue head. simple. But it may be changed later. If\r
138 // sub-1ms interrupt is supported, need to update the S-Mask\r
139 // here\r
140 //\r
141 if (Ep->DevSpeed == EFI_USB_SPEED_HIGH) {\r
142 QhHw->SMask = QH_MICROFRAME_0;\r
143 return ;\r
144 }\r
145 \r
146 //\r
147 // For low/full speed device, the transfer must go through\r
148 // the split transaction. Need to update three fields\r
149 // 1. SplitXState in the status\r
150 // 2. Microframe S-Mask\r
151 // 3. Microframe C-Mask\r
152 // UEFI USB doesn't exercise admission control. It simplely\r
153 // schedule the high speed transactions in microframe 0, and\r
154 // full/low speed transactions at microframe 1. This also\r
155 // avoid the use of FSTN.\r
156 //\r
157 QhHw->SMask = QH_MICROFRAME_1;\r
158 QhHw->CMask = QH_MICROFRAME_3 | QH_MICROFRAME_4 | QH_MICROFRAME_5;\r
159}\r
160\r
161/**\r
162 Allocate and initialize a EHCI queue head.\r
163 \r
164 @param Ehci The EHCI device.\r
165 @param Ep The endpoint to create queue head for.\r
166\r
167 @retval the pointer to the created queue head or NULL if failed to create one.\r
168\r
169**/\r
170PEI_EHC_QH *\r
171EhcCreateQh (\r
172 IN PEI_USB2_HC_DEV *Ehci,\r
173 IN USB_ENDPOINT *Ep\r
174 )\r
175{\r
176 PEI_EHC_QH *Qh;\r
177 QH_HW *QhHw;\r
178\r
179 Qh = UsbHcAllocateMem (Ehci, Ehci->MemPool, sizeof (PEI_EHC_QH));\r
180\r
181 if (Qh == NULL) {\r
182 return NULL;\r
183 }\r
184\r
185 Qh->Signature = EHC_QH_SIG;\r
186 Qh->NextQh = NULL;\r
187 Qh->Interval = Ep->PollRate;\r
188 \r
189 InitializeListHead (&Qh->Qtds);\r
190\r
191 QhHw = &Qh->QhHw;\r
192 QhHw->HorizonLink = QH_LINK (NULL, 0, TRUE);\r
193 QhHw->DeviceAddr = Ep->DevAddr;\r
194 QhHw->Inactive = 0;\r
195 QhHw->EpNum = Ep->EpAddr;\r
196 QhHw->EpSpeed = Ep->DevSpeed;\r
197 QhHw->DtCtrl = 0;\r
198 QhHw->ReclaimHead = 0;\r
199 QhHw->MaxPacketLen = (UINT32) Ep->MaxPacket;\r
200 QhHw->CtrlEp = 0;\r
201 QhHw->NakReload = QH_NAK_RELOAD;\r
202 QhHw->HubAddr = Ep->HubAddr;\r
203 QhHw->PortNum = Ep->HubPort;\r
204 QhHw->Multiplier = 1;\r
205 QhHw->DataToggle = Ep->Toggle;\r
206\r
207 if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) {\r
208 QhHw->Status |= QTD_STAT_DO_SS;\r
209 }\r
210\r
211 switch (Ep->Type) {\r
212 case EHC_CTRL_TRANSFER:\r
213 //\r
214 // Special initialization for the control transfer:\r
215 // 1. Control transfer initialize data toggle from each QTD\r
216 // 2. Set the Control Endpoint Flag (C) for low/full speed endpoint.\r
217 //\r
218 QhHw->DtCtrl = 1;\r
219\r
220 if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) {\r
221 QhHw->CtrlEp = 1;\r
222 }\r
223 break;\r
224\r
225 case EHC_INT_TRANSFER_ASYNC:\r
226 case EHC_INT_TRANSFER_SYNC:\r
227 //\r
228 // Special initialization for the interrupt transfer\r
229 // to set the S-Mask and C-Mask\r
230 //\r
231 QhHw->NakReload = 0;\r
232 EhcInitIntQh (Ep, QhHw);\r
233 break;\r
234\r
235 case EHC_BULK_TRANSFER:\r
236 if ((Ep->DevSpeed == EFI_USB_SPEED_HIGH) && (Ep->Direction == EfiUsbDataOut)) {\r
237 QhHw->Status |= QTD_STAT_DO_PING;\r
238 }\r
239\r
240 break;\r
241 }\r
242\r
243 return Qh;\r
244}\r
245\r
246/**\r
247 Convert the poll interval from application to that\r
248 be used by EHCI interface data structure. Only need\r
249 to get the max 2^n that is less than interval. UEFI\r
250 can't support high speed endpoint with a interval less\r
251 than 8 microframe because interval is specified in\r
252 the unit of ms (millisecond).\r
253 \r
254 @param Interval The interval to convert.\r
255\r
256 @retval The converted interval.\r
257\r
258**/\r
259UINTN\r
260EhcConvertPollRate (\r
261 IN UINTN Interval\r
262 )\r
263{\r
264 UINTN BitCount;\r
265\r
266 if (Interval == 0) {\r
267 return 1;\r
268 }\r
269 \r
270 //\r
271 // Find the index (1 based) of the highest non-zero bit\r
272 //\r
273 BitCount = 0;\r
274\r
275 while (Interval != 0) {\r
276 Interval >>= 1;\r
277 BitCount++;\r
278 }\r
279\r
280 return (UINTN)1 << (BitCount - 1);\r
281}\r
282\r
283/**\r
284 Free a list of QTDs.\r
285 \r
286 @param Ehc The EHCI device.\r
287 @param Qtds The list head of the QTD.\r
288\r
289**/\r
290VOID\r
291EhcFreeQtds (\r
292 IN PEI_USB2_HC_DEV *Ehc,\r
293 IN EFI_LIST_ENTRY *Qtds\r
294 )\r
295{\r
296 EFI_LIST_ENTRY *Entry;\r
297 EFI_LIST_ENTRY *Next;\r
298 PEI_EHC_QTD *Qtd;\r
299\r
300 EFI_LIST_FOR_EACH_SAFE (Entry, Next, Qtds) {\r
301 Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList);\r
302\r
303 RemoveEntryList (&Qtd->QtdList);\r
304 UsbHcFreeMem (Ehc->MemPool, Qtd, sizeof (PEI_EHC_QTD));\r
305 }\r
306}\r
307\r
308/**\r
309 Free an allocated URB. It is possible for it to be partially inited.\r
310 \r
311 @param Ehc The EHCI device.\r
312 @param Urb The URB to free.\r
313\r
314**/\r
315VOID\r
316EhcFreeUrb (\r
317 IN PEI_USB2_HC_DEV *Ehc,\r
318 IN PEI_URB *Urb\r
319 )\r
320{\r
321 if (Urb->Qh != NULL) {\r
322 //\r
323 // Ensure that this queue head has been unlinked from the\r
324 // schedule data structures. Free all the associated QTDs\r
325 //\r
326 EhcFreeQtds (Ehc, &Urb->Qh->Qtds);\r
327 UsbHcFreeMem (Ehc->MemPool, Urb->Qh, sizeof (PEI_EHC_QH));\r
328 }\r
329}\r
330\r
331/**\r
332 Create a list of QTDs for the URB.\r
333 \r
334 @param Ehc The EHCI device.\r
335 @param Urb The URB to create QTDs for.\r
336\r
337 @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for QTD.\r
338 @retval EFI_SUCCESS The QTDs are allocated for the URB.\r
339\r
340**/\r
341EFI_STATUS\r
342EhcCreateQtds (\r
343 IN PEI_USB2_HC_DEV *Ehc,\r
344 IN PEI_URB *Urb\r
345 )\r
346{\r
347 USB_ENDPOINT *Ep;\r
348 PEI_EHC_QH *Qh;\r
349 PEI_EHC_QTD *Qtd;\r
350 PEI_EHC_QTD *StatusQtd;\r
351 PEI_EHC_QTD *NextQtd;\r
352 EFI_LIST_ENTRY *Entry;\r
353 UINT32 AlterNext;\r
354 UINT8 Toggle;\r
355 UINTN Len;\r
356 UINT8 Pid;\r
357\r
358 ASSERT ((Urb != NULL) && (Urb->Qh != NULL));\r
359\r
360 //\r
361 // EHCI follows the alternet next QTD pointer if it meets\r
362 // a short read and the AlterNext pointer is valid. UEFI\r
363 // EHCI driver should terminate the transfer except the\r
364 // control transfer.\r
365 //\r
366 Toggle = 0;\r
367 Qh = Urb->Qh;\r
368 Ep = &Urb->Ep;\r
369 StatusQtd = NULL;\r
370 AlterNext = QTD_LINK (NULL, TRUE);\r
371\r
372 if (Ep->Direction == EfiUsbDataIn) {\r
373 AlterNext = QTD_LINK (Ehc->ShortReadStop, FALSE);\r
374 }\r
375 \r
376 //\r
377 // Build the Setup and status packets for control transfer\r
378 //\r
379 if (Urb->Ep.Type == EHC_CTRL_TRANSFER) {\r
380 Len = sizeof (EFI_USB_DEVICE_REQUEST);\r
381 Qtd = EhcCreateQtd (Ehc, Urb->RequestPhy, Len, QTD_PID_SETUP, 0, Ep->MaxPacket);\r
382\r
383 if (Qtd == NULL) {\r
384 return EFI_OUT_OF_RESOURCES;\r
385 }\r
386\r
387 InsertTailList (&Qh->Qtds, &Qtd->QtdList);\r
388\r
389 //\r
390 // Create the status packet now. Set the AlterNext to it. So, when\r
391 // EHCI meets a short control read, it can resume at the status stage.\r
392 // Use the opposite direction of the data stage, or IN if there is\r
393 // no data stage.\r
394 //\r
395 if (Ep->Direction == EfiUsbDataIn) {\r
396 Pid = QTD_PID_OUTPUT;\r
397 } else {\r
398 Pid = QTD_PID_INPUT;\r
399 }\r
400\r
401 StatusQtd = EhcCreateQtd (Ehc, NULL, 0, Pid, 1, Ep->MaxPacket);\r
402\r
403 if (StatusQtd == NULL) {\r
404 goto ON_ERROR;\r
405 }\r
406\r
407 if (Ep->Direction == EfiUsbDataIn) {\r
408 AlterNext = QTD_LINK (StatusQtd, FALSE);\r
409 }\r
410\r
411 Toggle = 1;\r
412 }\r
413\r
414 //\r
415 // Build the data packets for all the transfers\r
416 //\r
417 if (Ep->Direction == EfiUsbDataIn) {\r
418 Pid = QTD_PID_INPUT;\r
419 } else {\r
420 Pid = QTD_PID_OUTPUT;\r
421 }\r
422\r
423 Qtd = NULL;\r
424 Len = 0;\r
425\r
426 while (Len < Urb->DataLen) {\r
427 Qtd = EhcCreateQtd (\r
428 Ehc,\r
429 (UINT8 *) Urb->DataPhy + Len,\r
430 Urb->DataLen - Len,\r
431 Pid,\r
432 Toggle,\r
433 Ep->MaxPacket\r
434 );\r
435\r
436 if (Qtd == NULL) {\r
437 goto ON_ERROR;\r
438 }\r
439\r
440 Qtd->QtdHw.AltNext = AlterNext;\r
441 InsertTailList (&Qh->Qtds, &Qtd->QtdList);\r
442\r
443 //\r
444 // Switch the Toggle bit if odd number of packets are included in the QTD.\r
445 //\r
446 if (((Qtd->DataLen + Ep->MaxPacket - 1) / Ep->MaxPacket) % 2) {\r
447 Toggle = (UINT8) (1 - Toggle);\r
448 }\r
449\r
450 Len += Qtd->DataLen;\r
451 }\r
452 \r
453 //\r
454 // Insert the status packet for control transfer\r
455 //\r
456 if (Ep->Type == EHC_CTRL_TRANSFER) {\r
457 InsertTailList (&Qh->Qtds, &StatusQtd->QtdList);\r
458 }\r
459\r
460 //\r
461 // OK, all the QTDs needed are created. Now, fix the NextQtd point\r
462 //\r
463 EFI_LIST_FOR_EACH (Entry, &Qh->Qtds) {\r
464 Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList);\r
465\r
466 //\r
467 // break if it is the last entry on the list\r
468 //\r
469 if (Entry->ForwardLink == &Qh->Qtds) {\r
470 break;\r
471 }\r
472\r
473 NextQtd = EFI_LIST_CONTAINER (Entry->ForwardLink, PEI_EHC_QTD, QtdList);\r
474 Qtd->QtdHw.NextQtd = QTD_LINK (NextQtd, FALSE);\r
475 }\r
476\r
477 //\r
478 // Link the QTDs to the queue head\r
479 //\r
480 NextQtd = EFI_LIST_CONTAINER (Qh->Qtds.ForwardLink, PEI_EHC_QTD, QtdList);\r
481 Qh->QhHw.NextQtd = QTD_LINK (NextQtd, FALSE);\r
482 return EFI_SUCCESS;\r
483\r
484ON_ERROR:\r
485 EhcFreeQtds (Ehc, &Qh->Qtds);\r
486 return EFI_OUT_OF_RESOURCES;\r
487}\r
488\r
489/**\r
490 Create a new URB and its associated QTD.\r
491 \r
492 @param Ehc The EHCI device.\r
493 @param DevAddr The device address.\r
494 @param EpAddr Endpoint addrress & its direction.\r
495 @param DevSpeed The device speed.\r
496 @param Toggle Initial data toggle to use.\r
497 @param MaxPacket The max packet length of the endpoint.\r
498 @param Hub The transaction translator to use.\r
499 @param Type The transaction type.\r
500 @param Request The standard USB request for control transfer.\r
501 @param Data The user data to transfer.\r
502 @param DataLen The length of data buffer.\r
503 @param Callback The function to call when data is transferred.\r
504 @param Context The context to the callback.\r
505 @param Interval The interval for interrupt transfer.\r
506\r
507 @retval the pointer to the created URB or NULL.\r
508\r
509**/\r
510PEI_URB *\r
511EhcCreateUrb (\r
512 IN PEI_USB2_HC_DEV *Ehc,\r
513 IN UINT8 DevAddr,\r
514 IN UINT8 EpAddr, \r
515 IN UINT8 DevSpeed,\r
516 IN UINT8 Toggle,\r
517 IN UINTN MaxPacket,\r
518 IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Hub,\r
519 IN UINTN Type,\r
520 IN EFI_USB_DEVICE_REQUEST *Request,\r
521 IN VOID *Data,\r
522 IN UINTN DataLen,\r
523 IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback,\r
524 IN VOID *Context,\r
525 IN UINTN Interval\r
526 )\r
527{\r
528 USB_ENDPOINT *Ep;\r
529 EFI_PHYSICAL_ADDRESS PhyAddr;\r
530 EFI_STATUS Status;\r
531 UINTN Len;\r
532 PEI_URB *Urb;\r
533 VOID *Map;\r
534\r
535 \r
536 Map = NULL;\r
537 \r
538 Urb = Ehc->Urb;\r
539 Urb->Signature = EHC_URB_SIG;\r
540 InitializeListHead (&Urb->UrbList);\r
541\r
542 Ep = &Urb->Ep;\r
543 Ep->DevAddr = DevAddr;\r
544 Ep->EpAddr = (UINT8) (EpAddr & 0x0F);\r
545 Ep->Direction = (((EpAddr & 0x80) != 0) ? EfiUsbDataIn : EfiUsbDataOut);\r
546 Ep->DevSpeed = DevSpeed;\r
547 Ep->MaxPacket = MaxPacket;\r
548\r
549 Ep->HubAddr = 0;\r
550 Ep->HubPort = 0;\r
551\r
552 if (DevSpeed != EFI_USB_SPEED_HIGH) {\r
553 ASSERT (Hub != NULL);\r
554\r
555 Ep->HubAddr = Hub->TranslatorHubAddress;\r
556 Ep->HubPort = Hub->TranslatorPortNumber;\r
557 }\r
558\r
559 Ep->Toggle = Toggle;\r
560 Ep->Type = Type;\r
561 Ep->PollRate = EhcConvertPollRate (Interval);\r
562\r
563 Urb->Request = Request;\r
564 Urb->Data = Data;\r
565 Urb->DataLen = DataLen;\r
566 Urb->Callback = Callback;\r
567 Urb->Context = Context;\r
568 Urb->Qh = EhcCreateQh (Ehc, &Urb->Ep);\r
569\r
570 if (Urb->Qh == NULL) {\r
571 goto ON_ERROR;\r
572 }\r
573 \r
574 //\r
575 // Map the request and user data\r
576 //\r
577 if (Request != NULL) {\r
578 Len = sizeof (EFI_USB_DEVICE_REQUEST);\r
579 PhyAddr = (EFI_PHYSICAL_ADDRESS) (UINTN) Request ;\r
580 if ( (Len != sizeof (EFI_USB_DEVICE_REQUEST))) {\r
581 goto ON_ERROR;\r
582 }\r
583\r
584 Urb->RequestPhy = (VOID *) ((UINTN) PhyAddr);\r
585 Urb->RequestMap = Map;\r
586 }\r
587\r
588 if (Data != NULL) {\r
589 Len = DataLen;\r
590 PhyAddr = (EFI_PHYSICAL_ADDRESS) (UINTN) Data ;\r
591 if ( (Len != DataLen)) {\r
592 goto ON_ERROR;\r
593 }\r
594\r
595 Urb->DataPhy = (VOID *) ((UINTN) PhyAddr);\r
596 Urb->DataMap = Map;\r
597 }\r
598\r
599 Status = EhcCreateQtds (Ehc, Urb);\r
600\r
601 if (EFI_ERROR (Status)) {\r
602 goto ON_ERROR;\r
603 }\r
604\r
605 return Urb;\r
606\r
607ON_ERROR:\r
608 EhcFreeUrb (Ehc, Urb);\r
609 return NULL;\r
610}\r