3 EHCI transfer scheduling routines.
5 Copyright (c) 2007, Intel Corporation
6 All rights reserved. This program and the accompanying materials
7 are licensed and made available under the terms and conditions of the BSD License
8 which accompanies this distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
20 Create helper QTD/QH for the EHCI device.
22 @param Ehc The EHCI device.
24 @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for helper QTD/QH.
25 @retval EFI_SUCCESS Helper QH/QTD are created.
39 // Create an inactive Qtd to terminate the short packet read.
41 Qtd
= EhcCreateQtd (Ehc
, NULL
, 0, QTD_PID_INPUT
, 0, 64);
44 return EFI_OUT_OF_RESOURCES
;
47 Qtd
->QtdHw
.Status
= QTD_STAT_HALTED
;
48 Ehc
->ShortReadStop
= Qtd
;
51 // Create a QH to act as the EHC reclamation header.
52 // Set the header to loopback to itself.
56 Ep
.Direction
= EfiUsbDataIn
;
57 Ep
.DevSpeed
= EFI_USB_SPEED_HIGH
;
62 Ep
.Type
= EHC_BULK_TRANSFER
;
65 Qh
= EhcCreateQh (Ehc
, &Ep
);
68 return EFI_OUT_OF_RESOURCES
;
72 QhHw
->HorizonLink
= QH_LINK (QhHw
, EHC_TYPE_QH
, FALSE
);
73 QhHw
->Status
= QTD_STAT_HALTED
;
74 QhHw
->ReclaimHead
= 1;
75 Ehc
->ReclaimHead
= Qh
;
78 // Create a dummy QH to act as the terminator for periodical schedule
81 Ep
.Type
= EHC_INT_TRANSFER_SYNC
;
83 Qh
= EhcCreateQh (Ehc
, &Ep
);
86 return EFI_OUT_OF_RESOURCES
;
89 Qh
->QhHw
.Status
= QTD_STAT_HALTED
;
97 Initialize the schedule data structure such as frame list.
99 @param Ehc The EHCI device to init schedule data.
101 @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to init schedule data.
102 @retval EFI_SUCCESS The schedule data is initialized.
110 EFI_PCI_IO_PROTOCOL
*PciIo
;
112 EFI_PHYSICAL_ADDRESS PhyAddr
;
121 // First initialize the periodical schedule data:
122 // 1. Allocate and map the memory for the frame list
123 // 2. Create the help QTD/QH
124 // 3. Initialize the frame entries
125 // 4. Set the frame list register
130 Pages
= EFI_SIZE_TO_PAGES (Bytes
);
132 Status
= PciIo
->AllocateBuffer (
141 if (EFI_ERROR (Status
)) {
142 return EFI_OUT_OF_RESOURCES
;
145 Status
= PciIo
->Map (
147 EfiPciIoOperationBusMasterCommonBuffer
,
154 if (EFI_ERROR (Status
) || (Bytes
!= 4096)) {
155 PciIo
->FreeBuffer (PciIo
, Pages
, Buf
);
156 return EFI_OUT_OF_RESOURCES
;
159 Ehc
->PeriodFrameHost
= Buf
;
160 Ehc
->PeriodFrame
= (VOID
*) ((UINTN
) PhyAddr
);
161 Ehc
->PeriodFrameMap
= Map
;
162 Ehc
->High32bitAddr
= EHC_HIGH_32BIT (PhyAddr
);
165 // Init memory pool management then create the helper
166 // QTD/QH. If failed, previously allocated resources
167 // will be freed by EhcFreeSched
169 Ehc
->MemPool
= UsbHcInitMemPool (
171 EHC_BIT_IS_SET (Ehc
->HcCapParams
, HCCP_64BIT
),
175 if (Ehc
->MemPool
== NULL
) {
176 return EFI_OUT_OF_RESOURCES
;
179 Status
= EhcCreateHelpQ (Ehc
);
181 if (EFI_ERROR (Status
)) {
186 // Initialize the frame list entries then set the registers
188 Desc
= (UINT32
*) Ehc
->PeriodFrame
;
190 for (Index
= 0; Index
< EHC_FRAME_LEN
; Index
++) {
191 Desc
[Index
] = QH_LINK (Ehc
->PeriodOne
, EHC_TYPE_QH
, FALSE
);
194 EhcWriteOpReg (Ehc
, EHC_FRAME_BASE_OFFSET
, EHC_LOW_32BIT (Ehc
->PeriodFrame
));
197 // Second initialize the asynchronous schedule:
198 // Only need to set the AsynListAddr register to
199 // the reclamation header
201 EhcWriteOpReg (Ehc
, EHC_ASYNC_HEAD_OFFSET
, EHC_LOW_32BIT (Ehc
->ReclaimHead
));
207 Free the schedule data. It may be partially initialized.
209 @param Ehc The EHCI device.
219 EFI_PCI_IO_PROTOCOL
*PciIo
;
221 EhcWriteOpReg (Ehc
, EHC_FRAME_BASE_OFFSET
, 0);
222 EhcWriteOpReg (Ehc
, EHC_ASYNC_HEAD_OFFSET
, 0);
224 if (Ehc
->PeriodOne
!= NULL
) {
225 UsbHcFreeMem (Ehc
->MemPool
, Ehc
->PeriodOne
, sizeof (EHC_QH
));
226 Ehc
->PeriodOne
= NULL
;
229 if (Ehc
->ReclaimHead
!= NULL
) {
230 UsbHcFreeMem (Ehc
->MemPool
, Ehc
->ReclaimHead
, sizeof (EHC_QH
));
231 Ehc
->ReclaimHead
= NULL
;
234 if (Ehc
->ShortReadStop
!= NULL
) {
235 UsbHcFreeMem (Ehc
->MemPool
, Ehc
->ShortReadStop
, sizeof (EHC_QTD
));
236 Ehc
->ShortReadStop
= NULL
;
239 if (Ehc
->MemPool
!= NULL
) {
240 UsbHcFreeMemPool (Ehc
->MemPool
);
244 if (Ehc
->PeriodFrame
!= NULL
) {
246 ASSERT (PciIo
!= NULL
);
248 PciIo
->Unmap (PciIo
, Ehc
->PeriodFrameMap
);
252 EFI_SIZE_TO_PAGES (EFI_PAGE_SIZE
),
256 Ehc
->PeriodFrame
= NULL
;
262 Link the queue head to the asynchronous schedule list.
263 UEFI only supports one CTRL/BULK transfer at a time
264 due to its interfaces. This simplifies the AsynList
265 management: A reclamation header is always linked to
266 the AsyncListAddr, the only active QH is appended to it.
268 @param Ehc The EHCI device.
269 @param Qh The queue head to link.
283 // Append the queue head after the reclaim header, then
284 // fix the hardware visiable parts (EHCI R1.0 page 72).
285 // ReclaimHead is always linked to the EHCI's AsynListAddr.
287 Head
= Ehc
->ReclaimHead
;
289 Qh
->NextQh
= Head
->NextQh
;
292 Qh
->QhHw
.HorizonLink
= QH_LINK (Head
, EHC_TYPE_QH
, FALSE
);;
293 Head
->QhHw
.HorizonLink
= QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
298 Unlink a queue head from the asynchronous schedule list.
299 Need to synchronize with hardware.
301 @param Ehc The EHCI device.
302 @param Qh The queue head to unlink.
308 EhcUnlinkQhFromAsync (
316 ASSERT (Ehc
->ReclaimHead
->NextQh
== Qh
);
319 // Remove the QH from reclamation head, then update the hardware
320 // visiable part: Only need to loopback the ReclaimHead. The Qh
321 // is pointing to ReclaimHead (which is staill in the list).
323 Head
= Ehc
->ReclaimHead
;
325 Head
->NextQh
= Qh
->NextQh
;
328 Head
->QhHw
.HorizonLink
= QH_LINK (Head
, EHC_TYPE_QH
, FALSE
);
331 // Set and wait the door bell to synchronize with the hardware
333 Status
= EhcSetAndWaitDoorBell (Ehc
, EHC_GENERIC_TIMEOUT
);
335 if (EFI_ERROR (Status
)) {
336 DEBUG ((EFI_D_ERROR
, "EhcUnlinkQhFromAsync: Failed to synchronize with doorbell\n"));
342 Link a queue head for interrupt transfer to the periodic
343 schedule frame list. This code is very much the same as
346 @param Ehc The EHCI device.
347 @param Qh The queue head to link.
363 Frames
= Ehc
->PeriodFrame
;
365 for (Index
= 0; Index
< EHC_FRAME_LEN
; Index
+= Qh
->Interval
) {
367 // First QH can't be NULL because we always keep PeriodOne
368 // heads on the frame list
370 ASSERT (!EHC_LINK_TERMINATED (Frames
[Index
]));
371 Next
= EHC_ADDR (Ehc
->High32bitAddr
, Frames
[Index
]);
375 // Now, insert the queue head (Qh) into this frame:
376 // 1. Find a queue head with the same poll interval, just insert
377 // Qh after this queue head, then we are done.
379 // 2. Find the position to insert the queue head into:
380 // Previous head's interval is bigger than Qh's
381 // Next head's interval is less than Qh's
382 // Then, insert the Qh between then
384 while (Next
->Interval
> Qh
->Interval
) {
389 ASSERT (Next
!= NULL
);
392 // The entry may have been linked into the frame by early insertation.
393 // For example: if insert a Qh with Qh.Interval == 4, and there is a Qh
394 // with Qh.Interval == 8 on the frame. If so, we are done with this frame.
395 // It isn't necessary to compare all the QH with the same interval to
396 // Qh. This is because if there is other QH with the same interval, Qh
397 // should has been inserted after that at Frames[0] and at Frames[0] it is
398 // impossible for (Next == Qh)
404 if (Next
->Interval
== Qh
->Interval
) {
406 // If there is a QH with the same interval, it locates at
407 // Frames[0], and we can simply insert it after this QH. We
410 ASSERT ((Index
== 0) && (Qh
->NextQh
== NULL
));
418 Qh
->QhHw
.HorizonLink
= Prev
->QhHw
.HorizonLink
;
419 Prev
->QhHw
.HorizonLink
= QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
424 // OK, find the right position, insert it in. If Qh's next
425 // link has already been set, it is in position. This is
426 // guarranted by 2^n polling interval.
428 if (Qh
->NextQh
== NULL
) {
430 Qh
->QhHw
.HorizonLink
= QH_LINK (Next
, EHC_TYPE_QH
, FALSE
);
434 Frames
[Index
] = QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
437 Prev
->QhHw
.HorizonLink
= QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
444 Unlink an interrupt queue head from the periodic
447 @param Ehc The EHCI device.
448 @param Qh The queue head to unlink.
454 EhcUnlinkQhFromPeriod (
464 Frames
= Ehc
->PeriodFrame
;
466 for (Index
= 0; Index
< EHC_FRAME_LEN
; Index
+= Qh
->Interval
) {
468 // Frame link can't be NULL because we always keep PeroidOne
471 ASSERT (!EHC_LINK_TERMINATED (Frames
[Index
]));
472 This
= EHC_ADDR (Ehc
->High32bitAddr
, Frames
[Index
]);
476 // Walk through the frame's QH list to find the
477 // queue head to remove
479 while ((This
!= NULL
) && (This
!= Qh
)) {
485 // Qh may have already been unlinked from this frame
486 // by early action. See the comments in EhcLinkQhToPeriod.
494 // Qh is the first entry in the frame
496 Frames
[Index
] = Qh
->QhHw
.HorizonLink
;
498 Prev
->NextQh
= Qh
->NextQh
;
499 Prev
->QhHw
.HorizonLink
= Qh
->QhHw
.HorizonLink
;
506 Check the URB's execution result and update the URB's
509 @param Ehc The EHCI device.
510 @param Urb The URB to check result.
512 @return Whether the result of URB transfer is finialized.
527 ASSERT ((Ehc
!= NULL
) && (Urb
!= NULL
) && (Urb
->Qh
!= NULL
));
532 Urb
->Result
= EFI_USB_NOERROR
;
534 if (EhcIsHalt (Ehc
) || EhcIsSysError (Ehc
)) {
535 Urb
->Result
|= EFI_USB_ERR_SYSTEM
;
539 EFI_LIST_FOR_EACH (Entry
, &Urb
->Qh
->Qtds
) {
540 Qtd
= EFI_LIST_CONTAINER (Entry
, EHC_QTD
, QtdList
);
542 State
= (UINT8
) QtdHw
->Status
;
544 if (EHC_BIT_IS_SET (State
, QTD_STAT_HALTED
)) {
546 // EHCI will halt the queue head when met some error.
547 // If it is halted, the result of URB is finialized.
549 if ((State
& QTD_STAT_ERR_MASK
) == 0) {
550 Urb
->Result
|= EFI_USB_ERR_STALL
;
553 if (EHC_BIT_IS_SET (State
, QTD_STAT_BABBLE_ERR
)) {
554 Urb
->Result
|= EFI_USB_ERR_BABBLE
;
557 if (EHC_BIT_IS_SET (State
, QTD_STAT_BUFF_ERR
)) {
558 Urb
->Result
|= EFI_USB_ERR_BUFFER
;
561 if (EHC_BIT_IS_SET (State
, QTD_STAT_TRANS_ERR
) && (QtdHw
->ErrCnt
== 0)) {
562 Urb
->Result
|= EFI_USB_ERR_TIMEOUT
;
568 } else if (EHC_BIT_IS_SET (State
, QTD_STAT_ACTIVE
)) {
570 // The QTD is still active, no need to check furthur.
572 Urb
->Result
|= EFI_USB_ERR_NOTEXECUTE
;
579 // This QTD is finished OK or met short packet read. Update the
580 // transfer length if it isn't a setup.
582 if (QtdHw
->Pid
!= QTD_PID_SETUP
) {
583 Urb
->Completed
+= Qtd
->DataLen
- QtdHw
->TotalBytes
;
586 if ((QtdHw
->TotalBytes
!= 0) && (QtdHw
->Pid
== QTD_PID_INPUT
)) {
587 EhcDumpQh (Urb
->Qh
, "Short packet read", FALSE
);
590 // Short packet read condition. If it isn't a setup transfer,
591 // no need to check furthur: the queue head will halt at the
592 // ShortReadStop. If it is a setup transfer, need to check the
593 // Status Stage of the setup transfer to get the finial result
595 if (QtdHw
->AltNext
== QTD_LINK (Ehc
->ShortReadStop
, FALSE
)) {
596 DEBUG ((EFI_D_INFO
, "EhcCheckUrbResult: Short packet read, break\n"));
602 DEBUG ((EFI_D_INFO
, "EhcCheckUrbResult: Short packet read, continue\n"));
609 // Return the data toggle set by EHCI hardware, bulk and interrupt
610 // transfer will use this to initialize the next transaction. For
611 // Control transfer, it always start a new data toggle sequence for
614 // NOTICE: don't move DT update before the loop, otherwise there is
615 // a race condition that DT is wrong.
617 Urb
->DataToggle
= (UINT8
) Urb
->Qh
->QhHw
.DataToggle
;
624 Execute the transfer by polling the URB. This is a synchronous operation.
626 @param Ehc The EHCI device.
627 @param Urb The URB to execute.
628 @param TimeOut The time to wait before abort, in millisecond.
630 @return EFI_DEVICE_ERROR The transfer failed due to transfer error.
631 @return EFI_TIMEOUT The transfer failed due to time out.
632 @return EFI_SUCCESS The transfer finished OK.
647 Status
= EFI_SUCCESS
;
648 Loop
= (TimeOut
* EHC_1_MILLISECOND
/ EHC_SYNC_POLL_INTERVAL
) + 1;
651 for (Index
= 0; Index
< Loop
; Index
++) {
652 Finished
= EhcCheckUrbResult (Ehc
, Urb
);
658 gBS
->Stall (EHC_SYNC_POLL_INTERVAL
);
662 DEBUG ((EFI_D_ERROR
, "EhcExecTransfer: transfer not finished in %dms\n", TimeOut
));
663 EhcDumpQh (Urb
->Qh
, NULL
, FALSE
);
665 Status
= EFI_TIMEOUT
;
667 } else if (Urb
->Result
!= EFI_USB_NOERROR
) {
668 DEBUG ((EFI_D_ERROR
, "EhcExecTransfer: transfer failed with %x\n", Urb
->Result
));
669 EhcDumpQh (Urb
->Qh
, NULL
, FALSE
);
671 Status
= EFI_DEVICE_ERROR
;
679 Delete a single asynchronous interrupt transfer for
680 the device and endpoint.
682 @param Ehc The EHCI device.
683 @param DevAddr The address of the target device.
684 @param EpNum The endpoint of the target.
685 @param DataToggle Return the next data toggle to use.
687 @retval EFI_SUCCESS An asynchronous transfer is removed.
688 @retval EFI_NOT_FOUND No transfer for the device is found.
692 EhciDelAsyncIntTransfer (
696 OUT UINT8
*DataToggle
702 EFI_USB_DATA_DIRECTION Direction
;
704 Direction
= (((EpNum
& 0x80) != 0) ? EfiUsbDataIn
: EfiUsbDataOut
);
707 EFI_LIST_FOR_EACH_SAFE (Entry
, Next
, &Ehc
->AsyncIntTransfers
) {
708 Urb
= EFI_LIST_CONTAINER (Entry
, URB
, UrbList
);
710 if ((Urb
->Ep
.DevAddr
== DevAddr
) && (Urb
->Ep
.EpAddr
== EpNum
) &&
711 (Urb
->Ep
.Direction
== Direction
)) {
713 // Check the URB status to retrieve the next data toggle
714 // from the associated queue head.
716 EhcCheckUrbResult (Ehc
, Urb
);
717 *DataToggle
= Urb
->DataToggle
;
719 EhcUnlinkQhFromPeriod (Ehc
, Urb
->Qh
);
720 RemoveEntryList (&Urb
->UrbList
);
722 gBS
->FreePool (Urb
->Data
);
723 EhcFreeUrb (Ehc
, Urb
);
728 return EFI_NOT_FOUND
;
733 Remove all the asynchronous interrutp transfers.
735 @param Ehc The EHCI device.
741 EhciDelAllAsyncIntTransfers (
749 EFI_LIST_FOR_EACH_SAFE (Entry
, Next
, &Ehc
->AsyncIntTransfers
) {
750 Urb
= EFI_LIST_CONTAINER (Entry
, URB
, UrbList
);
752 EhcUnlinkQhFromPeriod (Ehc
, Urb
->Qh
);
753 RemoveEntryList (&Urb
->UrbList
);
755 gBS
->FreePool (Urb
->Data
);
756 EhcFreeUrb (Ehc
, Urb
);
762 Flush data from PCI controller specific address to mapped system
765 @param Ehc The EHCI device.
766 @param Urb The URB to unmap.
768 @retval EFI_SUCCESS Success to flush data to mapped system memory.
769 @retval EFI_DEVICE_ERROR Fail to flush data to mapped system memory.
773 EhcFlushAsyncIntMap (
779 EFI_PHYSICAL_ADDRESS PhyAddr
;
780 EFI_PCI_IO_PROTOCOL_OPERATION MapOp
;
781 EFI_PCI_IO_PROTOCOL
*PciIo
;
788 if (Urb
->Ep
.Direction
== EfiUsbDataIn
) {
789 MapOp
= EfiPciIoOperationBusMasterWrite
;
791 MapOp
= EfiPciIoOperationBusMasterRead
;
794 Status
= PciIo
->Unmap (PciIo
, Urb
->DataMap
);
795 if (EFI_ERROR (Status
)) {
801 Status
= PciIo
->Map (PciIo
, MapOp
, Urb
->Data
, &Len
, &PhyAddr
, &Map
);
802 if (EFI_ERROR (Status
) || (Len
!= Urb
->DataLen
)) {
806 Urb
->DataPhy
= (VOID
*) ((UINTN
) PhyAddr
);
811 return EFI_DEVICE_ERROR
;
816 Update the queue head for next round of asynchronous transfer.
818 @param Urb The URB to update.
824 EhcUpdateAsyncRequest (
837 if (Urb
->Result
== EFI_USB_NOERROR
) {
840 EFI_LIST_FOR_EACH (Entry
, &Urb
->Qh
->Qtds
) {
841 Qtd
= EFI_LIST_CONTAINER (Entry
, EHC_QTD
, QtdList
);
843 if (FirstQtd
== NULL
) {
848 // Update the QTD for next round of transfer. Host control
849 // may change dt/Total Bytes to Transfer/C_Page/Cerr/Status/
850 // Current Offset. These fields need to be updated. DT isn't
851 // used by interrupt transfer. It uses DT in queue head.
852 // Current Offset is in Page[0], only need to reset Page[0]
853 // to initial data buffer.
856 QtdHw
->Status
= QTD_STAT_ACTIVE
;
857 QtdHw
->ErrCnt
= QTD_MAX_ERR
;
859 QtdHw
->TotalBytes
= (UINT32
) Qtd
->DataLen
;
860 QtdHw
->Page
[0] = EHC_LOW_32BIT (Qtd
->Data
);
864 // Update QH for next round of transfer. Host control only
865 // touch the fields in transfer overlay area. Only need to
866 // zero out the overlay area and set NextQtd to the first
867 // QTD. DateToggle bit is left untouched.
869 QhHw
= &Urb
->Qh
->QhHw
;
870 QhHw
->CurQtd
= QTD_LINK (0, TRUE
);
878 QhHw
->TotalBytes
= 0;
880 for (Index
= 0; Index
< 5; Index
++) {
881 QhHw
->Page
[Index
] = 0;
882 QhHw
->PageHigh
[Index
] = 0;
885 QhHw
->NextQtd
= QTD_LINK (FirstQtd
, FALSE
);
893 Interrupt transfer periodic check handler.
895 @param Event Interrupt event.
896 @param Context Pointer to USB2_HC_DEV.
902 EhcMoniteAsyncRequests (
916 OldTpl
= gBS
->RaiseTPL (EHC_TPL
);
917 Ehc
= (USB2_HC_DEV
*) Context
;
919 EFI_LIST_FOR_EACH_SAFE (Entry
, Next
, &Ehc
->AsyncIntTransfers
) {
920 Urb
= EFI_LIST_CONTAINER (Entry
, URB
, UrbList
);
923 // Check the result of URB execution. If it is still
924 // active, check the next one.
926 Finished
= EhcCheckUrbResult (Ehc
, Urb
);
933 // Flush any PCI posted write transactions from a PCI host
934 // bridge to system memory.
936 Status
= EhcFlushAsyncIntMap (Ehc
, Urb
);
937 if (EFI_ERROR (Status
)) {
938 DEBUG ((EFI_D_ERROR
, "EhcMoniteAsyncRequests: Fail to Flush AsyncInt Mapped Memeory\n"));
942 // Allocate a buffer then copy the transferred data for user.
943 // If failed to allocate the buffer, update the URB for next
944 // round of transfer. Ignore the data of this round.
948 if (Urb
->Result
== EFI_USB_NOERROR
) {
949 ASSERT (Urb
->Completed
<= Urb
->DataLen
);
951 ProcBuf
= AllocatePool (Urb
->Completed
);
953 if (ProcBuf
== NULL
) {
954 EhcUpdateAsyncRequest (Urb
);
958 CopyMem (ProcBuf
, Urb
->Data
, Urb
->Completed
);
961 EhcUpdateAsyncRequest (Urb
);
964 // Leave error recovery to its related device driver. A
965 // common case of the error recovery is to re-submit the
966 // interrupt transfer which is linked to the head of the
967 // list. This function scans from head to tail. So the
968 // re-submitted interrupt transfer's callback function
969 // will not be called again in this round. Don't touch this
970 // URB after the callback, it may have been removed by the
973 if (Urb
->Callback
!= NULL
) {
975 // Restore the old TPL, USB bus maybe connect device in
976 // his callback. Some drivers may has a lower TPL restriction.
978 gBS
->RestoreTPL (OldTpl
);
979 (Urb
->Callback
) (ProcBuf
, Urb
->Completed
, Urb
->Context
, Urb
->Result
);
980 OldTpl
= gBS
->RaiseTPL (EHC_TPL
);
983 if (ProcBuf
!= NULL
) {
984 gBS
->FreePool (ProcBuf
);
988 gBS
->RestoreTPL (OldTpl
);