3 EHCI transfer scheduling routines.
5 Copyright (c) 2007 - 2009, 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.
217 EFI_PCI_IO_PROTOCOL
*PciIo
;
219 EhcWriteOpReg (Ehc
, EHC_FRAME_BASE_OFFSET
, 0);
220 EhcWriteOpReg (Ehc
, EHC_ASYNC_HEAD_OFFSET
, 0);
222 if (Ehc
->PeriodOne
!= NULL
) {
223 UsbHcFreeMem (Ehc
->MemPool
, Ehc
->PeriodOne
, sizeof (EHC_QH
));
224 Ehc
->PeriodOne
= NULL
;
227 if (Ehc
->ReclaimHead
!= NULL
) {
228 UsbHcFreeMem (Ehc
->MemPool
, Ehc
->ReclaimHead
, sizeof (EHC_QH
));
229 Ehc
->ReclaimHead
= NULL
;
232 if (Ehc
->ShortReadStop
!= NULL
) {
233 UsbHcFreeMem (Ehc
->MemPool
, Ehc
->ShortReadStop
, sizeof (EHC_QTD
));
234 Ehc
->ShortReadStop
= NULL
;
237 if (Ehc
->MemPool
!= NULL
) {
238 UsbHcFreeMemPool (Ehc
->MemPool
);
242 if (Ehc
->PeriodFrame
!= NULL
) {
244 ASSERT (PciIo
!= NULL
);
246 PciIo
->Unmap (PciIo
, Ehc
->PeriodFrameMap
);
250 EFI_SIZE_TO_PAGES (EFI_PAGE_SIZE
),
254 Ehc
->PeriodFrame
= NULL
;
260 Link the queue head to the asynchronous schedule list.
261 UEFI only supports one CTRL/BULK transfer at a time
262 due to its interfaces. This simplifies the AsynList
263 management: A reclamation header is always linked to
264 the AsyncListAddr, the only active QH is appended to it.
266 @param Ehc The EHCI device.
267 @param Qh The queue head to link.
279 // Append the queue head after the reclaim header, then
280 // fix the hardware visiable parts (EHCI R1.0 page 72).
281 // ReclaimHead is always linked to the EHCI's AsynListAddr.
283 Head
= Ehc
->ReclaimHead
;
285 Qh
->NextQh
= Head
->NextQh
;
288 Qh
->QhHw
.HorizonLink
= QH_LINK (Head
, EHC_TYPE_QH
, FALSE
);;
289 Head
->QhHw
.HorizonLink
= QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
294 Unlink a queue head from the asynchronous schedule list.
295 Need to synchronize with hardware.
297 @param Ehc The EHCI device.
298 @param Qh The queue head to unlink.
302 EhcUnlinkQhFromAsync (
310 ASSERT (Ehc
->ReclaimHead
->NextQh
== Qh
);
313 // Remove the QH from reclamation head, then update the hardware
314 // visiable part: Only need to loopback the ReclaimHead. The Qh
315 // is pointing to ReclaimHead (which is staill in the list).
317 Head
= Ehc
->ReclaimHead
;
319 Head
->NextQh
= Qh
->NextQh
;
322 Head
->QhHw
.HorizonLink
= QH_LINK (Head
, EHC_TYPE_QH
, FALSE
);
325 // Set and wait the door bell to synchronize with the hardware
327 Status
= EhcSetAndWaitDoorBell (Ehc
, EHC_GENERIC_TIMEOUT
);
329 if (EFI_ERROR (Status
)) {
330 DEBUG ((EFI_D_ERROR
, "EhcUnlinkQhFromAsync: Failed to synchronize with doorbell\n"));
336 Link a queue head for interrupt transfer to the periodic
337 schedule frame list. This code is very much the same as
340 @param Ehc The EHCI device.
341 @param Qh The queue head to link.
355 Frames
= Ehc
->PeriodFrame
;
357 for (Index
= 0; Index
< EHC_FRAME_LEN
; Index
+= Qh
->Interval
) {
359 // First QH can't be NULL because we always keep PeriodOne
360 // heads on the frame list
362 ASSERT (!EHC_LINK_TERMINATED (Frames
[Index
]));
363 Next
= EHC_ADDR (Ehc
->High32bitAddr
, Frames
[Index
]);
367 // Now, insert the queue head (Qh) into this frame:
368 // 1. Find a queue head with the same poll interval, just insert
369 // Qh after this queue head, then we are done.
371 // 2. Find the position to insert the queue head into:
372 // Previous head's interval is bigger than Qh's
373 // Next head's interval is less than Qh's
374 // Then, insert the Qh between then
376 while (Next
->Interval
> Qh
->Interval
) {
381 ASSERT (Next
!= NULL
);
384 // The entry may have been linked into the frame by early insertation.
385 // For example: if insert a Qh with Qh.Interval == 4, and there is a Qh
386 // with Qh.Interval == 8 on the frame. If so, we are done with this frame.
387 // It isn't necessary to compare all the QH with the same interval to
388 // Qh. This is because if there is other QH with the same interval, Qh
389 // should has been inserted after that at Frames[0] and at Frames[0] it is
390 // impossible for (Next == Qh)
396 if (Next
->Interval
== Qh
->Interval
) {
398 // If there is a QH with the same interval, it locates at
399 // Frames[0], and we can simply insert it after this QH. We
402 ASSERT ((Index
== 0) && (Qh
->NextQh
== NULL
));
410 Qh
->QhHw
.HorizonLink
= Prev
->QhHw
.HorizonLink
;
411 Prev
->QhHw
.HorizonLink
= QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
416 // OK, find the right position, insert it in. If Qh's next
417 // link has already been set, it is in position. This is
418 // guarranted by 2^n polling interval.
420 if (Qh
->NextQh
== NULL
) {
422 Qh
->QhHw
.HorizonLink
= QH_LINK (Next
, EHC_TYPE_QH
, FALSE
);
426 Frames
[Index
] = QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
429 Prev
->QhHw
.HorizonLink
= QH_LINK (Qh
, EHC_TYPE_QH
, FALSE
);
436 Unlink an interrupt queue head from the periodic
439 @param Ehc The EHCI device.
440 @param Qh The queue head to unlink.
444 EhcUnlinkQhFromPeriod (
454 Frames
= Ehc
->PeriodFrame
;
456 for (Index
= 0; Index
< EHC_FRAME_LEN
; Index
+= Qh
->Interval
) {
458 // Frame link can't be NULL because we always keep PeroidOne
461 ASSERT (!EHC_LINK_TERMINATED (Frames
[Index
]));
462 This
= EHC_ADDR (Ehc
->High32bitAddr
, Frames
[Index
]);
466 // Walk through the frame's QH list to find the
467 // queue head to remove
469 while ((This
!= NULL
) && (This
!= Qh
)) {
475 // Qh may have already been unlinked from this frame
476 // by early action. See the comments in EhcLinkQhToPeriod.
484 // Qh is the first entry in the frame
486 Frames
[Index
] = Qh
->QhHw
.HorizonLink
;
488 Prev
->NextQh
= Qh
->NextQh
;
489 Prev
->QhHw
.HorizonLink
= Qh
->QhHw
.HorizonLink
;
496 Check the URB's execution result and update the URB's
499 @param Ehc The EHCI device.
500 @param Urb The URB to check result.
502 @return Whether the result of URB transfer is finialized.
517 ASSERT ((Ehc
!= NULL
) && (Urb
!= NULL
) && (Urb
->Qh
!= NULL
));
522 Urb
->Result
= EFI_USB_NOERROR
;
524 if (EhcIsHalt (Ehc
) || EhcIsSysError (Ehc
)) {
525 Urb
->Result
|= EFI_USB_ERR_SYSTEM
;
529 EFI_LIST_FOR_EACH (Entry
, &Urb
->Qh
->Qtds
) {
530 Qtd
= EFI_LIST_CONTAINER (Entry
, EHC_QTD
, QtdList
);
532 State
= (UINT8
) QtdHw
->Status
;
534 if (EHC_BIT_IS_SET (State
, QTD_STAT_HALTED
)) {
536 // EHCI will halt the queue head when met some error.
537 // If it is halted, the result of URB is finialized.
539 if ((State
& QTD_STAT_ERR_MASK
) == 0) {
540 Urb
->Result
|= EFI_USB_ERR_STALL
;
543 if (EHC_BIT_IS_SET (State
, QTD_STAT_BABBLE_ERR
)) {
544 Urb
->Result
|= EFI_USB_ERR_BABBLE
;
547 if (EHC_BIT_IS_SET (State
, QTD_STAT_BUFF_ERR
)) {
548 Urb
->Result
|= EFI_USB_ERR_BUFFER
;
551 if (EHC_BIT_IS_SET (State
, QTD_STAT_TRANS_ERR
) && (QtdHw
->ErrCnt
== 0)) {
552 Urb
->Result
|= EFI_USB_ERR_TIMEOUT
;
558 } else if (EHC_BIT_IS_SET (State
, QTD_STAT_ACTIVE
)) {
560 // The QTD is still active, no need to check furthur.
562 Urb
->Result
|= EFI_USB_ERR_NOTEXECUTE
;
569 // This QTD is finished OK or met short packet read. Update the
570 // transfer length if it isn't a setup.
572 if (QtdHw
->Pid
!= QTD_PID_SETUP
) {
573 Urb
->Completed
+= Qtd
->DataLen
- QtdHw
->TotalBytes
;
576 if ((QtdHw
->TotalBytes
!= 0) && (QtdHw
->Pid
== QTD_PID_INPUT
)) {
577 EhcDumpQh (Urb
->Qh
, "Short packet read", FALSE
);
580 // Short packet read condition. If it isn't a setup transfer,
581 // no need to check furthur: the queue head will halt at the
582 // ShortReadStop. If it is a setup transfer, need to check the
583 // Status Stage of the setup transfer to get the finial result
585 if (QtdHw
->AltNext
== QTD_LINK (Ehc
->ShortReadStop
, FALSE
)) {
586 DEBUG ((EFI_D_INFO
, "EhcCheckUrbResult: Short packet read, break\n"));
592 DEBUG ((EFI_D_INFO
, "EhcCheckUrbResult: Short packet read, continue\n"));
599 // Return the data toggle set by EHCI hardware, bulk and interrupt
600 // transfer will use this to initialize the next transaction. For
601 // Control transfer, it always start a new data toggle sequence for
604 // NOTICE: don't move DT update before the loop, otherwise there is
605 // a race condition that DT is wrong.
607 Urb
->DataToggle
= (UINT8
) Urb
->Qh
->QhHw
.DataToggle
;
614 Execute the transfer by polling the URB. This is a synchronous operation.
616 @param Ehc The EHCI device.
617 @param Urb The URB to execute.
618 @param TimeOut The time to wait before abort, in millisecond.
620 @return EFI_DEVICE_ERROR The transfer failed due to transfer error.
621 @return EFI_TIMEOUT The transfer failed due to time out.
622 @return EFI_SUCCESS The transfer finished OK.
637 Status
= EFI_SUCCESS
;
638 Loop
= (TimeOut
* EHC_1_MILLISECOND
/ EHC_SYNC_POLL_INTERVAL
) + 1;
641 for (Index
= 0; Index
< Loop
; Index
++) {
642 Finished
= EhcCheckUrbResult (Ehc
, Urb
);
648 gBS
->Stall (EHC_SYNC_POLL_INTERVAL
);
652 DEBUG ((EFI_D_ERROR
, "EhcExecTransfer: transfer not finished in %dms\n", (UINT32
)TimeOut
));
653 EhcDumpQh (Urb
->Qh
, NULL
, FALSE
);
655 Status
= EFI_TIMEOUT
;
657 } else if (Urb
->Result
!= EFI_USB_NOERROR
) {
658 DEBUG ((EFI_D_ERROR
, "EhcExecTransfer: transfer failed with %x\n", Urb
->Result
));
659 EhcDumpQh (Urb
->Qh
, NULL
, FALSE
);
661 Status
= EFI_DEVICE_ERROR
;
669 Delete a single asynchronous interrupt transfer for
670 the device and endpoint.
672 @param Ehc The EHCI device.
673 @param DevAddr The address of the target device.
674 @param EpNum The endpoint of the target.
675 @param DataToggle Return the next data toggle to use.
677 @retval EFI_SUCCESS An asynchronous transfer is removed.
678 @retval EFI_NOT_FOUND No transfer for the device is found.
682 EhciDelAsyncIntTransfer (
686 OUT UINT8
*DataToggle
692 EFI_USB_DATA_DIRECTION Direction
;
694 Direction
= (((EpNum
& 0x80) != 0) ? EfiUsbDataIn
: EfiUsbDataOut
);
697 EFI_LIST_FOR_EACH_SAFE (Entry
, Next
, &Ehc
->AsyncIntTransfers
) {
698 Urb
= EFI_LIST_CONTAINER (Entry
, URB
, UrbList
);
700 if ((Urb
->Ep
.DevAddr
== DevAddr
) && (Urb
->Ep
.EpAddr
== EpNum
) &&
701 (Urb
->Ep
.Direction
== Direction
)) {
703 // Check the URB status to retrieve the next data toggle
704 // from the associated queue head.
706 EhcCheckUrbResult (Ehc
, Urb
);
707 *DataToggle
= Urb
->DataToggle
;
709 EhcUnlinkQhFromPeriod (Ehc
, Urb
->Qh
);
710 RemoveEntryList (&Urb
->UrbList
);
712 gBS
->FreePool (Urb
->Data
);
713 EhcFreeUrb (Ehc
, Urb
);
718 return EFI_NOT_FOUND
;
723 Remove all the asynchronous interrutp transfers.
725 @param Ehc The EHCI device.
729 EhciDelAllAsyncIntTransfers (
737 EFI_LIST_FOR_EACH_SAFE (Entry
, Next
, &Ehc
->AsyncIntTransfers
) {
738 Urb
= EFI_LIST_CONTAINER (Entry
, URB
, UrbList
);
740 EhcUnlinkQhFromPeriod (Ehc
, Urb
->Qh
);
741 RemoveEntryList (&Urb
->UrbList
);
743 gBS
->FreePool (Urb
->Data
);
744 EhcFreeUrb (Ehc
, Urb
);
750 Flush data from PCI controller specific address to mapped system
753 @param Ehc The EHCI device.
754 @param Urb The URB to unmap.
756 @retval EFI_SUCCESS Success to flush data to mapped system memory.
757 @retval EFI_DEVICE_ERROR Fail to flush data to mapped system memory.
761 EhcFlushAsyncIntMap (
767 EFI_PHYSICAL_ADDRESS PhyAddr
;
768 EFI_PCI_IO_PROTOCOL_OPERATION MapOp
;
769 EFI_PCI_IO_PROTOCOL
*PciIo
;
776 if (Urb
->Ep
.Direction
== EfiUsbDataIn
) {
777 MapOp
= EfiPciIoOperationBusMasterWrite
;
779 MapOp
= EfiPciIoOperationBusMasterRead
;
782 Status
= PciIo
->Unmap (PciIo
, Urb
->DataMap
);
783 if (EFI_ERROR (Status
)) {
789 Status
= PciIo
->Map (PciIo
, MapOp
, Urb
->Data
, &Len
, &PhyAddr
, &Map
);
790 if (EFI_ERROR (Status
) || (Len
!= Urb
->DataLen
)) {
794 Urb
->DataPhy
= (VOID
*) ((UINTN
) PhyAddr
);
799 return EFI_DEVICE_ERROR
;
804 Update the queue head for next round of asynchronous transfer.
806 @param Urb The URB to update.
810 EhcUpdateAsyncRequest (
823 if (Urb
->Result
== EFI_USB_NOERROR
) {
826 EFI_LIST_FOR_EACH (Entry
, &Urb
->Qh
->Qtds
) {
827 Qtd
= EFI_LIST_CONTAINER (Entry
, EHC_QTD
, QtdList
);
829 if (FirstQtd
== NULL
) {
834 // Update the QTD for next round of transfer. Host control
835 // may change dt/Total Bytes to Transfer/C_Page/Cerr/Status/
836 // Current Offset. These fields need to be updated. DT isn't
837 // used by interrupt transfer. It uses DT in queue head.
838 // Current Offset is in Page[0], only need to reset Page[0]
839 // to initial data buffer.
842 QtdHw
->Status
= QTD_STAT_ACTIVE
;
843 QtdHw
->ErrCnt
= QTD_MAX_ERR
;
845 QtdHw
->TotalBytes
= (UINT32
) Qtd
->DataLen
;
846 QtdHw
->Page
[0] = EHC_LOW_32BIT (Qtd
->Data
);
850 // Update QH for next round of transfer. Host control only
851 // touch the fields in transfer overlay area. Only need to
852 // zero out the overlay area and set NextQtd to the first
853 // QTD. DateToggle bit is left untouched.
855 QhHw
= &Urb
->Qh
->QhHw
;
856 QhHw
->CurQtd
= QTD_LINK (0, TRUE
);
864 QhHw
->TotalBytes
= 0;
866 for (Index
= 0; Index
< 5; Index
++) {
867 QhHw
->Page
[Index
] = 0;
868 QhHw
->PageHigh
[Index
] = 0;
871 QhHw
->NextQtd
= QTD_LINK (FirstQtd
, FALSE
);
879 Interrupt transfer periodic check handler.
881 @param Event Interrupt event.
882 @param Context Pointer to USB2_HC_DEV.
886 EhcMonitorAsyncRequests (
900 OldTpl
= gBS
->RaiseTPL (EHC_TPL
);
901 Ehc
= (USB2_HC_DEV
*) Context
;
903 EFI_LIST_FOR_EACH_SAFE (Entry
, Next
, &Ehc
->AsyncIntTransfers
) {
904 Urb
= EFI_LIST_CONTAINER (Entry
, URB
, UrbList
);
907 // Check the result of URB execution. If it is still
908 // active, check the next one.
910 Finished
= EhcCheckUrbResult (Ehc
, Urb
);
917 // Flush any PCI posted write transactions from a PCI host
918 // bridge to system memory.
920 Status
= EhcFlushAsyncIntMap (Ehc
, Urb
);
921 if (EFI_ERROR (Status
)) {
922 DEBUG ((EFI_D_ERROR
, "EhcMonitorAsyncRequests: Fail to Flush AsyncInt Mapped Memeory\n"));
926 // Allocate a buffer then copy the transferred data for user.
927 // If failed to allocate the buffer, update the URB for next
928 // round of transfer. Ignore the data of this round.
932 if (Urb
->Result
== EFI_USB_NOERROR
) {
933 ASSERT (Urb
->Completed
<= Urb
->DataLen
);
935 ProcBuf
= AllocatePool (Urb
->Completed
);
937 if (ProcBuf
== NULL
) {
938 EhcUpdateAsyncRequest (Urb
);
942 CopyMem (ProcBuf
, Urb
->Data
, Urb
->Completed
);
945 EhcUpdateAsyncRequest (Urb
);
948 // Leave error recovery to its related device driver. A
949 // common case of the error recovery is to re-submit the
950 // interrupt transfer which is linked to the head of the
951 // list. This function scans from head to tail. So the
952 // re-submitted interrupt transfer's callback function
953 // will not be called again in this round. Don't touch this
954 // URB after the callback, it may have been removed by the
957 if (Urb
->Callback
!= NULL
) {
959 // Restore the old TPL, USB bus maybe connect device in
960 // his callback. Some drivers may has a lower TPL restriction.
962 gBS
->RestoreTPL (OldTpl
);
963 (Urb
->Callback
) (ProcBuf
, Urb
->Completed
, Urb
->Context
, Urb
->Result
);
964 OldTpl
= gBS
->RaiseTPL (EHC_TPL
);
967 if (ProcBuf
!= NULL
) {
972 gBS
->RestoreTPL (OldTpl
);