#include #include #include #include #include #include #include #include #include #include // Things to note: // PME_ena bit should be active before the 82558 is set into low power mode // Default for WOL should generate wake up event after a HW Reset // Fixed Packet Filtering // Need to verify that the micro code is loaded and Micro Machine is active // Clock signal is active on PCI clock // Address Matching // Need to enable IAMatch_Wake_En bit and the MCMatch_Wake_En bit is set // ARP Wakeup // Need to set BRCST DISABL bet to 0 (broadcast enable) // To handle VLAN set the VLAN_ARP bit // IP address needs to be configured with 16 least significant bits // Set the IP Address in the IP_Address configuration word. // Fixed WakeUp Filters: // There are 3ight different fixed WakeUp Filters // ( Unicast, Multicast, Arp. etc). // Link Status Event // Set Link_Status_Wakeup Enable bit. // Flexible filtering: // Supports: ARP packets, Directed, Magic Packet and Link Event // Flexible Filtering Overview: // driver should program micro-code before setting card into low power // Incoming packets are compared against the loadable microcode. If PME is // is enabled then, the system is woken up. // Segments are defined in book - but not implemented here. // WakeUp Packet -that causes the machine to wake up will be stored // in the Micro Machine temporary storage area so that the driver can read it. // Software Work: // Power Down: // OS requests the driver to go to a low power state // Software Pends request // SW sets CU and RU to idle by issuing a Selective Reset to the device // 3rd portion .- Wake Up Segments defintion // The above three segments are loaded as on chain. The last CB must have // its EL bit set. // Device can now be powered down. // Software driver completes OS request // OS then physically switches the Device to low power state // // Power Up: // OS powers up the Device // OS tells the SW that it is now in D0 // driver should NOT initialize the Device. It should NOT issue a Self Test // Driver Initiates a PORT DUMP command // Device dumps its internal registers including the wakeup frame storage area // SW reads the PME register // SW reads the WakeUp Frame Data, analyzes it and acts accordingly // SW restores its cvonfiguration and and resumes normal operation. // // // Power Management definitions from the Intel Handbook // // // Definitions from Table 4.2, Pg 4.9 // of the 10/100 Mbit Ethernet Family Software Technical // Reference Manual // #define PMC_Offset 0xDE #define E100_PMC_WAKE_FROM_D0 0x1 #define E100_PMC_WAKE_FROM_D1 0x2 #define E100_PMC_WAKE_FROM_D2 0x4 #define E100_PMC_WAKE_FROM_D3HOT 0x8 #define E100_PMC_WAKE_FROM_D3_AUX 0x10 // // Load Programmable filter definintions. // Taken from C-19 from the Software Reference Manual. // It has examples too. The opcode used for load is 0x80000 // #define BIT_15_13 0xA000 #define CB_LOAD_PROG_FILTER BIT_3 #define CU_LOAD_PROG_FILTER_EL BIT_7 #define CU_SUCCEED_LOAD_PROG_FILTER BIT_15_13 #define CB_FILTER_EL BIT_7 #define CB_FILTER_PREDEFINED_FIX BIT_6 #define CB_FILTER_ARP_WAKEUP BIT_3 #define CB_FILTER_IA_WAKEUP BIT_1 #define CU_SCB_NULL ((UINT)-1) #pragma pack( push, enter_include1, 1 ) // // Define the PM Capabilities register in the device // portion of the PCI config space // typedef struct _MP_PM_CAP_REG { USHORT UnInteresting:11; USHORT PME_Support:5; } MP_PM_CAP_REG; // // Define the PM Control/Status Register // typedef struct _MP_PMCSR { USHORT PowerState:2; // Power State; USHORT Res:2; // reserved USHORT DynData:1; // Ignored USHORT Res1:3; // Reserved USHORT PME_En:1; // Enable device to set the PME Event; USHORT DataSel:4; // Unused USHORT DataScale:2; // Data Scale - Unused USHORT PME_Status:1; // PME Status - Sticky bit; } MP_PMCSR ; typedef struct _MP_PM_PCI_SPACE { UCHAR Stuff[PMC_Offset]; // PM capabilites MP_PM_CAP_REG PMCaps; // PM Control Status Register MP_PMCSR PMCSR; } MP_PM_PCI_SPACE , *PMP_PM_PCI_SPACE ; // // This is the Programmable Filter Command Structure // typedef struct _MP_PROG_FILTER_COMM_STRUCT { // CB Status Word USHORT CBStatus; // CB Command Word USHORT CBCommand; //Next CB PTR == ffff ffff ULONG NextCBPTR; //Programmable Filters ULONG FilterData[16]; } MP_PROG_FILTER_COMM_STRUCT,*PMP_PROG_FILTER_COMM_STRUCT; typedef struct _MP_PMDR { // Status of the PME bit UCHAR PMEStatus:1; // Is the TCO busy UCHAR TCORequest:1; // Force TCO indication UCHAR TCOForce:1; // Is the TCO Ready UCHAR TCOReady:1; // Reserved UCHAR Reserved:1; // Has an InterestingPacket been received UCHAR InterestingPacket:1; // Has a Magic Packet been received UCHAR MagicPacket:1; // Has the Link Status been changed UCHAR LinkStatus:1; } MP_PMDR , *PMP_PMDR; //------------------------------------------------------------------------- // Structure used to set up a programmable filter. // This is overlayed over the Control/Status Register (CSR) //------------------------------------------------------------------------- typedef struct _CSR_FILTER_STRUC { // Status- used to verify if the load prog filter command // has been accepted .set to 0xa000 USHORT ScbStatus; // SCB Status register // Set to an opcode of 0x8 // UCHAR ScbCommandLow; // SCB Command register (low byte) // 80. Low + High gives the required opcode 0x80080000 UCHAR ScbCommandHigh; // SCB Command register (high byte) // Set to NULL ff ff ff ff ULONG NextPointer; // SCB General pointer // Set to a hardcoded filter, Arp + IA Match, + IP address union { ULONG u32; struct { UCHAR IPAddress[2]; UCHAR Reserved; UCHAR Set; }PreDefined; }Programmable; // Wake UP Filter union } CSR_FILTER_STRUC, *PCSR_FILTER_STRUC; #pragma pack( pop, enter_include1 ) #define MP_CLEAR_PMDR(pPMDR) (*pPMDR) = ((*pPMDR) | 0xe0); // clear the 3 uppermost bits in the PMDR //------------------------------------------------------------------------- // L O C A L P R O T O T Y P E S //------------------------------------------------------------------------- __inline NDIS_STATUS MPIssueScbPoMgmtCommand( IN PMP_ADAPTER Adapter, IN PCSR_FILTER_STRUC pFilter, IN BOOLEAN WaitForScb ); VOID MPCreateProgrammableFilter ( IN PMP_WAKE_PATTERN pMpWakePattern , IN PUCHAR pFilter, IN OUT PULONG pNext ); // // Macros used to walk a doubly linked list. Only macros that are not defined in ndis.h // The List Next macro will work on Single and Doubly linked list as Flink is a common // field name in both // /* PLIST_ENTRY ListNext ( IN PLIST_ENTRY ); PSINGLE_LIST_ENTRY ListNext ( IN PSINGLE_LIST_ENTRY ); */ #define ListNext(_pL) (_pL)->Flink /* PLIST_ENTRY ListPrev ( IN LIST_ENTRY * ); */ #define ListPrev(_pL) (_pL)->Blink //------------------------------------------------------------------------- // P O W E R M G M T F U N C T I O N S //------------------------------------------------------------------------- PUCHAR HwReadPowerPMDR( IN PMP_ADAPTER Adapter ) /*++ Routine Description: This routine will Hardware's PM registers Arguments: Adapter Pointer to our adapter Return Value: NDIS_STATUS_SUCCESS NDIS_STATUS_HARD_ERRORS --*/ { UCHAR PMDR =0; PUCHAR pPMDR = NULL; #define CSR_SIZE sizeof (*Adapter->CSRAddress) ASSERT (CSR_SIZE == 0x18); pPMDR = 0x18 + (PUCHAR)Adapter->CSRAddress ; PMDR = *pPMDR; return pPMDR; } NDIS_STATUS MPWritePciSlotInfo( PMP_ADAPTER pAdapter, ULONG Offset, PVOID pValue, ULONG SizeofValue ) { ULONG ulResult; NDIS_STATUS Status; ulResult = NdisWritePciSlotInformation( pAdapter->AdapterHandle, 0, Offset, pValue, SizeofValue); ASSERT (ulResult == SizeofValue); // What do we do in case of failure; // if (ulResult == SizeofValue) { Status = NDIS_STATUS_SUCCESS; } else { Status = NDIS_STATUS_FAILURE; } return Status; } NDIS_STATUS MPReadPciSlotInfo( PMP_ADAPTER pAdapter, ULONG Offset, PVOID pValue, ULONG SizeofValue ) { ULONG ulResult; NDIS_STATUS Status; ulResult = NdisReadPciSlotInformation( pAdapter->AdapterHandle, 0, Offset, pValue, SizeofValue); ASSERT (ulResult == SizeofValue); // What do we do in case of failure; // if (ulResult == SizeofValue) { Status = NDIS_STATUS_SUCCESS; } else { Status = NDIS_STATUS_FAILURE; } return Status; } NDIS_STATUS MpClearPME_En ( IN PMP_ADAPTER pAdapter, IN MP_PMCSR PMCSR ) { NDIS_STATUS Status; UINT ulResult; PMCSR.PME_En = 0; Status = MPWritePciSlotInfo( pAdapter, FIELD_OFFSET(MP_PM_PCI_SPACE, PMCSR), (PVOID)&PMCSR, sizeof(PMCSR)); return Status; } VOID MpExtractPMInfoFromPciSpace( PMP_ADAPTER pAdapter, PUCHAR pPciConfig ) /*++ Routine Description: Looks at the PM information in the device specific section of the PCI Config space. Interprets the register values and stores it in the adapter structure Definitions from Table 4.2 & 4.3, Pg 4-9 & 4-10 of the 10/100 Mbit Ethernet Family Software Technical Reference Manual Arguments: Adapter Pointer to our adapter pPciConfig Pointer to Common Pci Space Return Value: --*/ { PMP_PM_PCI_SPACE pPmPciConfig = (PMP_PM_PCI_SPACE )pPciConfig; PMP_POWER_MGMT pPoMgmt = &pAdapter->PoMgmt; MP_PMCSR PMCSR; // // First interpret the PM Capabities register // { MP_PM_CAP_REG PmCaps; PmCaps = pPmPciConfig->PMCaps; if(PmCaps.PME_Support & E100_PMC_WAKE_FROM_D0) { pAdapter->PoMgmt.bWakeFromD0 = TRUE; } if(PmCaps.PME_Support & E100_PMC_WAKE_FROM_D1) { pAdapter->PoMgmt.bWakeFromD1 = TRUE; } if(PmCaps.PME_Support & E100_PMC_WAKE_FROM_D2) { pAdapter->PoMgmt.bWakeFromD2 = TRUE; } if(PmCaps.PME_Support & E100_PMC_WAKE_FROM_D3HOT) { pAdapter->PoMgmt.bWakeFromD3Hot = TRUE; } if(PmCaps.PME_Support & E100_PMC_WAKE_FROM_D3_AUX) { pAdapter->PoMgmt.bWakeFromD3Aux = TRUE; } } // // Interpret the PM Control/Status Register // { PMCSR = pPmPciConfig->PMCSR; if (PMCSR.PME_En == 1) { // // PME is enabled. Clear the PME_En bit. // So that it is not asserted // MpClearPME_En (pAdapter,PMCSR); } //pPoMgmt->PowerState = PMCSR.PowerState; } } VOID MPSetPowerLowPrivate( PMP_ADAPTER pAdapter ) /*++ Routine Description: The section follows the steps mentioned in Section C.2.6.2 of the Reference Manual. Arguments: Adapter Pointer to our adapter Return Value: --*/ { CSR_FILTER_STRUC Filter; NDIS_STATUS Status = NDIS_STATUS_SUCCESS; USHORT IntStatus; MP_PMCSR PMCSR; NdisZeroMemory (&Filter, sizeof (Filter)); do { // // Before issue the command to low power state, we should disable the // interrup and ack all the pending interrupts, then set the adapter's power to // low state. // NICDisableInterrupt(pAdapter); NIC_ACK_INTERRUPT(pAdapter, IntStatus); pAdapter->CurrentPowerState = pAdapter->NextPowerState; // // If the driver should wake up the machine // if (pAdapter->WakeUpEnable != 0) { // // Send the WakeUp Patter to the nic MPIssueScbPoMgmtCommand(pAdapter, &Filter, TRUE); // // Section C.2.6.2 - The driver needs to wait for the CU to idle // The above function already waits for the CU to idle // ASSERT ((pAdapter->CSRAddress->ScbStatus & SCB_CUS_MASK) == SCB_CUS_IDLE); } else { MPReadPciSlotInfo(pAdapter, FIELD_OFFSET(MP_PM_PCI_SPACE, PMCSR), (PVOID)&PMCSR, sizeof(PMCSR)); if (PMCSR.PME_En == 1) { // // PME is enabled. Clear the PME_En bit. // So that it is not asserted // MpClearPME_En (pAdapter,PMCSR); } // // Set the driver to lower power state by OS // } } while (FALSE); } NDIS_STATUS MPSetPowerD0Private ( IN MP_ADAPTER* pAdapter ) { PUCHAR pPMDR; NDIS_STATUS Status; do { // Dump the packet if necessary //Cause of Wake Up pPMDR = HwReadPowerPMDR(pAdapter); NICInitializeAdapter(pAdapter); // Clear the PMDR MP_CLEAR_PMDR(pPMDR); NICIssueSelectiveReset(pAdapter); } while (FALSE); return NDIS_STATUS_SUCCESS; } VOID MPSetPowerWorkItem( IN PNDIS_WORK_ITEM pWorkItem, IN PVOID pContext ) { // // Call the appropriate function // // // Complete the original request // } VOID HwSetWakeUpConfigure( IN PMP_ADAPTER pAdapter, PUCHAR pPoMgmtConfigType, UINT WakeUpParameter ) { if (MPIsPoMgmtSupported( pAdapter) == TRUE) { (*pPoMgmtConfigType)= ((*pPoMgmtConfigType)| CB_WAKE_ON_LINK_BYTE9 |CB_WAKE_ON_ARP_PKT_BYTE9 ); } } NDIS_STATUS MPSetUpFilterCB( IN PMP_ADAPTER pAdapter ) { NDIS_STATUS Status = NDIS_STATUS_SUCCESS; PCB_HEADER_STRUC NonTxCmdBlockHdr = (PCB_HEADER_STRUC)pAdapter->NonTxCmdBlock; PFILTER_CB_STRUC pFilterCb = (PFILTER_CB_STRUC)NonTxCmdBlockHdr; ULONG Curr = 0; ULONG Next = 0; PLIST_ENTRY pPatternEntry = ListNext(&pAdapter->PoMgmt.PatternList) ; DBGPRINT(MP_TRACE, ("--> HwSetupIAAddress\n")); NdisZeroMemory (pFilterCb, sizeof(*pFilterCb)); // Individual Address Setup NonTxCmdBlockHdr->CbStatus = 0; NonTxCmdBlockHdr->CbCommand = CB_EL_BIT | CB_LOAD_PROG_FILTER; NonTxCmdBlockHdr->CbLinkPointer = DRIVER_NULL; // go through each filter in the list. while (pPatternEntry != (&pAdapter->PoMgmt.PatternList)) { PMP_WAKE_PATTERN pWakeUpPattern = NULL; PNDIS_PM_PACKET_PATTERN pCurrPattern = NULL;; // initialize local variables pWakeUpPattern = CONTAINING_RECORD(pPatternEntry, MP_WAKE_PATTERN, linkListEntry); // increment the iterator pPatternEntry = ListNext (pPatternEntry); // Update the Curr Array Pointer Curr = Next; // Create the Programmable filter for this device. MPCreateProgrammableFilter (pWakeUpPattern , (PUCHAR)&pFilterCb->Pattern[Curr], &Next); if (Next >=16) { break; } } { // Set the EL bit on the last pattern PUCHAR pLastPattern = (PUCHAR) &pFilterCb->Pattern[Curr]; // Get to bit 31 pLastPattern[3] |= CB_FILTER_EL ; } ASSERT(pAdapter->CSRAddress->ScbCommandLow == 0) // Wait for the CU to Idle before giving it this command if(!WaitScb(pAdapter)) { Status = NDIS_STATUS_HARD_ERRORS; } return Status; } NDIS_STATUS MPIssueScbPoMgmtCommand( IN PMP_ADAPTER pAdapter, IN PCSR_FILTER_STRUC pNewFilter, IN BOOLEAN WaitForScb ) { NDIS_STATUS Status = NDIS_STATUS_FAILURE; do { // Set up SCB to issue this command Status = MPSetUpFilterCB(pAdapter); if (Status != NDIS_STATUS_SUCCESS) { break; } // Submit the configure command to the chip, and wait for it to complete. pAdapter->CSRAddress->ScbGeneralPointer = pAdapter->NonTxCmdBlockPhys; Status = D100SubmitCommandBlockAndWait(pAdapter); if(Status != NDIS_STATUS_SUCCESS) { Status = NDIS_STATUS_NOT_ACCEPTED; break; } } while (FALSE); return Status; } NDIS_STATUS MPCalculateE100PatternForFilter ( IN PUCHAR pFrame, IN ULONG FrameLength, IN PUCHAR pMask, IN ULONG MaskLength, OUT PULONG pSignature ) /*++ Routine Description: This function outputs the E100 specific Pattern Signature used to wake up the machine. Section C.2.4 - CRC word calculation of a Flexible Filer Arguments: pFrame - Pattern Set by the protocols FrameLength - Length of the Pattern pMask - Mask set by the Protocols MaskLength - Length of the Mask pSignature - caller allocated return structure Return Value: Returns Success Failure - if the Pattern is greater than 129 bytes --*/ { const ULONG Coefficients = 0x04c11db7; ULONG Signature = 0; ULONG n = 0; ULONG i= 0; PUCHAR pCurrentMaskByte = pMask - 1; // init to -1 ULONG MaskOffset = 0; ULONG BitOffsetInMask = 0; ULONG MaskBit = 0; BOOLEAN fIgnoreCurrentByte = FALSE; ULONG ShiftBy = 0; UCHAR FrameByte = 0; NDIS_STATUS Status = NDIS_STATUS_FAILURE; *pSignature = 0; do { if (FrameLength > 128) { Status = NDIS_STATUS_FAILURE; break; } // The E100 driver can only accept 3 DWORDS of Mask in a single pattern if (MaskLength > (3*sizeof(ULONG))) { Status = NDIS_STATUS_FAILURE; break; } for (n=i=0;(n<128) && (n < FrameLength); ++n) { // The first half deals with the question - // Is the nth Frame byte to be included in the Filter // BitOffsetInMask = (n % 8); if (BitOffsetInMask == 0) { // // We need to move to a new byte. // [0] for 0th byte, [1] for 8th byte, [2] for 16th byte, etc. // MaskOffset = n/8; // This is the new byte we need to go // // if (MaskOffset == MaskLength) { break; } pCurrentMaskByte ++; ASSERT (*pCurrentMaskByte == pMask[n/8]); } // Now look at the actual bit in the mask MaskBit = 1 << BitOffsetInMask ; // If the current Mask Bit is set in the Mask then // we need to use it in the CRC calculation, otherwise we ignore it fIgnoreCurrentByte = ! (MaskBit & pCurrentMaskByte[0]); if (fIgnoreCurrentByte) { continue; } // We are suppossed to take in the current byte as part of the CRC calculation // Initialize the variables FrameByte = pFrame[n]; ShiftBy = (i % 3 ) * 8; ASSERT (ShiftBy!= 24); // Bit 24 is never used if (Signature & 0x80000000) { Signature = ((Signature << 1) ^ ( FrameByte << ShiftBy) ^ Coefficients); } else { Signature = ((Signature << 1 ) ^ (FrameByte << ShiftBy)); } ++i; } // Clear bits 22-31 Signature &= 0x00ffffff; // Update the result *pSignature = Signature; // We have succeeded Status = NDIS_STATUS_SUCCESS; } while (FALSE); return Status; } VOID MPCreateProgrammableFilter ( IN PMP_WAKE_PATTERN pMpWakePattern , IN PUCHAR pFilter, IN OUT PULONG pNext ) /*++ Routine Description: This function outputs the E100 specific Pattern Signature used to wake up the machine. Section C.2.4 - Load Programmable Filter page C.20 Arguments: pMpWakePattern - Filter will be created for this pattern, pFilter - Filter will be stored here, pNext - Used for validation . This Ulong will also be incremented by the size of the filter (in ulongs) Return Value: --*/ { PUCHAR pCurrentByte = pFilter; ULONG NumBytesWritten = 0; PULONG pCurrentUlong = (PULONG)pFilter; PNDIS_PM_PACKET_PATTERN pNdisPattern = (PNDIS_PM_PACKET_PATTERN)(&pMpWakePattern->Pattern[0]); ULONG LengthOfFilter = 0; // Is there enough room for this pattern // { // Length in DWORDS LengthOfFilter = pNdisPattern->MaskSize /4; if (pNdisPattern->MaskSize % 4 != 0) { LengthOfFilter++; } // Increment LengthOfFilter to account for the 1st DWORD LengthOfFilter++; // We are only allowed 16 DWORDS in a filter if (*pNext + LengthOfFilter >= 16) { // Failure - early exit return; } } // Clear the Predefined bit; already cleared in the previous function. // first , initialize - *pCurrentUlong = 0; // Mask Length goes into Bits 27-29 of the 1st DWORD. MaskSize is measured in DWORDs { ULONG dwMaskSize = pNdisPattern->MaskSize /4; ULONG dwMLen = 0; // If there is a remainder a remainder then increment if (pNdisPattern->MaskSize % 4 != 0) { dwMaskSize++; } // // If we fail this assertion, it means our // MaskSize is greater than 16 bytes. // This filter should have been failed upfront at the time of the request // ASSERT (0 < dwMaskSize <5); // // In the Spec, 0 - Single DWORD maske, 001 - 2 DWORD mask, // 011 - 3 DWORD mask, 111 - 4 Dword Mask. // if (dwMaskSize == 1) dwMLen = 0; if (dwMaskSize == 2) dwMLen = 1; if (dwMaskSize == 3) dwMLen = 3; if (dwMaskSize == 4) dwMLen = 7; // Adjust the Mlen, so it is in the correct position dwMLen = (dwMLen << 3); if (dwMLen != 0) { ASSERT (dwMLen <= 0x38 && dwMLen >= 0x08); } // These go into bits 27,28,29 (bits 3,4 and 5 of the 4th byte) pCurrentByte[3] |= dwMLen ; } // Add the signature to bits 0-23 of the 1st DWORD { PUCHAR pSignature = (PUCHAR)&pMpWakePattern->Signature; // Bits 0-23 are also the 1st three bytes of the DWORD pCurrentByte[0] = pSignature[0]; pCurrentByte[1] = pSignature[1]; pCurrentByte[2] = pSignature[2]; } // Lets move to the next DWORD. Init variables pCurrentByte += 4 ; NumBytesWritten = 4; pCurrentUlong = (PULONG)pCurrentByte; // We Copy in the Mask over here { // The Mask is at the end of the pattern PUCHAR pMask = (PUCHAR)pNdisPattern + sizeof(*pNdisPattern); Dump (pMask,pNdisPattern->MaskSize, 0,1); NdisMoveMemory (pCurrentByte, pMask, pNdisPattern->MaskSize); NumBytesWritten += pNdisPattern->MaskSize; } // Update the output value { ULONG NumUlongs = (NumBytesWritten /4); if ((NumBytesWritten %4) != 0) { NumUlongs ++; } ASSERT (NumUlongs == LengthOfFilter); *pNext = *pNext + NumUlongs; } return; }