Sample System Implementation
This is short sample implementing a thermal control service interface. This sample assumes one thermal sensor and one thermal control device accessible via ACPI. For an ARM implementation, FF-A and Hafnium is assumed. For x86/x64, an eSPI transport is assumed and direct (Non-Secure) access is made from there.
FFA Device Definition
#![allow(unused)] fn main() { Device(\\_SB_.FFA0) { Name(_HID, "MSFT000C") OperationRegion(AFFH, FFixedHw, 4, 144) Field(AFFH, BufferAcc, NoLock, Preserve) { AccessAs(BufferAcc, 0x1), FFAC, 1152 } // Other components check this to make sure FFA is available Method(AVAL, 0, Serialized) { Return(One) } // Register notification events from FFA Method(_RNY, 0, Serialized) { Return( Package() { Package(0x2) { // Events for Management Service ToUUID("330c1273-fde5-4757-9819-5b6539037502"), Buffer() {0x1,0x0} // Register event 0x1 }, Package(0x2) { // Events for Thermal service ToUUID("31f56da7-593c-4d72-a4b3-8fc7171ac073"), Buffer() {0x1,0x0,0x2,0x0,0x3,0x0} // Register events 0x1, 0x2, 0x3 }, Package(0x2) { // Events for input device ToUUID("e3168a99-4a57-4a2b-8c5e-11bcfec73406"), Buffer() {0x1,0x0} // Register event 0x1 for LID } } ) } Method(_NFY, 2, Serialized) { // Arg0 == UUID // Arg1 == Notify ID // Management Service Events If(LEqual(ToUUID("330c1273-fde5-4757-9819-5b6539037502"),Arg0)) { Switch(Arg1) { Case(1) { // Test Notification Event Notify(\\_SB.ECT0,0x20) } } } // Thermal service events If(LEqual(ToUUID("31f56da7-593c-4d72-a4b3-8fc7171ac073"),Arg0)) { Switch(Arg1) { Case(1) { // Temp crossed low threshold Notify(\\_SB.SKIN,0x80) } Case(2) { // Temp crossed high threshold Notify(\\_SB.SKIN,0x81) } Case(3) { // Critical temperature event Notify(\\_SB.SKIN,0x82) } } } // Input Device Events If(LEqual(ToUUID("e3168a99-4a57-4a2b-8c5e-11bcfec73406"),Arg0)) { Switch(Arg1) { Case(1) { // LID event Notify(\\_SB._LID,0x80) } } } } } }
Memory Mapped Interface via FFA for UCSI
Note for this implementation of memory mapped interface to work the memory must be marked as reserved by UEFI and not used by the OS and direct access also given to the corresponding service in secure world.
#![allow(unused)] fn main() { Device(USBC) { Name(_HID,EISAID(“USBC000”)) Name(_CID,EISAID(“PNP0CA0”)) Name(_UID,1) Name(_DDN, “USB Type-C”) Name(_ADR,0x0) OperationRegion(USBC, SystemMemory, UCSI_PHYS_MEM, 0x30) Field(USBC,AnyAcc,Lock,Preserve) { // USB C Mailbox Interface VERS,16, // PPM-\>OPM Version RES, 16, // Reservied CCI, 32, // PPM-\>OPM CCI Indicator CTRL,64, // OPM-\>PPM Control Messages MSGI,128, // OPM-\>PPM Message In MSGO,128, // PPM-\>OPM Message Out } Method(_DSM,4,Serialized,0,UnknownObj, {BuffObj, IntObj,IntObj,PkgObj}) { // Compare passed in UUID to Supported UUID If(LEqual(Arg0,ToUUID(“6f8398c2-7ca4-11e4-ad36-631042b5008f”))) { // Use FFA to send Notification event down to copy data to EC If(LEqual(\\_SB.FFA0.AVAL,One)) { Name(BUFF, Buffer(144){}) // Create buffer for send/recv data CreateByteField(BUFF,0,STAT) // Out – Status for req/rsp CreateByteField(BUFF,1,LENG) // In/Out – Bytes in req, updates bytes returned CreateField(BUFF,16,128,UUID) // UUID of service CreateByteField(BUFF,18, CMDD) // In – First byte of command CreateField(BUFF,144,1024,FIFD) // Out – Msg data // Create Doorbell Event Store(20, LENG) Store(0x0, CMDD) // UCSI set doorbell Store(ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), UUID) Store(Store(BUFF, \_SB_.FFA0.FFAC), BUFF) } // End AVAL } // End UUID } // End DSM } }
Thermal ACPI Interface for FFA
This sample code shows one Microsoft Thermal zone for SKIN and then a thermal device THRM for implementing customized IO.
#![allow(unused)] fn main() { // Sample Definition of FAN ACPI Device(SKIN) { Name(_HID, "MSFT000A") Method(_TMP, 0x0, Serialized) { If(LEqual(\\_SB.FFA0.AVAL,One)) { Name(BUFF, Buffer(30){}) CreateByteField(BUFF,0,STAT) // Out – Status for req/rsp CreateByteField(BUFF,1,LENG) // In/Out – Bytes in req, updates bytes returned CreateField(BUFF,16,128,UUID) // UUID of service CreateByteField(BUFF,18,CMDD) // Command register CreateByteField(BUFF,19,TZID) // Temp Sensor ID CreateDWordField(BUFF,26,RTMP) // Output Data Store(20, LENG) Store(0x1, CMDD) // EC_THM_GET_TMP Store(0x2, TZID) // Temp zone ID for SKIIN Store(ToUUID("31f56da7-593c-4d72-a4b3-8fc7171ac073"), UUID) Store(Store(BUFF, \\_SB_.FFA0.FFAC), BUFF) If(LEqual(STAT,0x0) ) // Check FF-A successful? { Return (RTMP) } } Return (Ones) } // Arg0 Temp sensor ID // Arg1 Package with Low and High set points Method(THRS,0x2, Serialized) { If(LEqual(\\_SB.FFA0.AVAL,One)) { Name(BUFF, Buffer(32){}) CreateByteField(BUFF,0,STAT) // Out – Status for req/rsp CreateByteField(BUFF,1,LENG) // In/Out – Bytes in req, updates bytes returned CreateField(BUFF,16,128,UUID) // UUID of service CreateByteField(BUFF,18,CMDD) // Command register CreateByteField(BUFF,19,TZID) // Temp Sensor ID CreateDwordField(BUFF,20,VTIM) // Timeout CreateDwordField(BUFF,24,VLO) // Low Threshold CreateDwordField(BUFF,28,VHI) // High Threshold CreateDWordField(BUFF,18,TSTS) // Output Data Store(ToUUID("31f56da7-593c-4d72-a4b3-8fc7171ac073"), UUID) Store(32, LENG) Store(0x2, CMDD) // EC_THM_SET_THRS Store(Arg0, TZID) Store(DeRefOf(Index(Arg1,0)),VTIM) Store(DeRefOf(Index(Arg1,1)),VLO) Store(DeRefOf(Index(Arg1,2)),VHI) Store(Store(BUFF, \\_SB_.FFA0.FFAC), BUFF) If(LEqual(STAT,0x0) ) // Check FF-A successful? { Return (TSTS) } } Return (0x3) // Hardware failure } // Arg0 GUID 1f0849fc-a845-4fcf-865c-4101bf8e8d79 // Arg1 Revision // Arg2 Function Index // Arg3 Function dependent Method(_DSM, 0x4, Serialized) { If(LEqual(ToUuid("1f0849fc-a845-4fcf-865c-4101bf8e8d79"),Arg0)) { Switch(Arg2) { Case (0) { Return(0x3) // Support Function 0 and Function 1 } Case (1) { Return( THRS(0x2, Arg3) ) // Call to function to set threshold } } } Return(0x3) } } Device(THRM) { Name(_HID, "MSFT000B") // Arg0 Instance ID // Arg1 UUID of variable // Return (Status,Value) Method(GVAR,2,Serialized) { If(LEqual(\\_SB.FFA0.AVAL,One)) { Name(BUFF, Buffer(38){}) CreateByteField(BUFF,0,STAT) // Out – Status for req/rsp CreateByteField(BUFF,1,LENG) // In/Out – Bytes in req, updates bytes returned CreateField(BUFF,16,128,UUID) // UUID of service CreateByteField(BUFF,18,CMDD) // Command register CreateByteField(BUFF,19,INST) // Instance ID CreateWordField(BUFF,20,VLEN) // 16-bit variable length CreateField(BUFF,176,128,VUID) // UUID of variable to read CreateField(BUFF,208,64,RVAL) // Output Data Store(ToUUID("31f56da7-593c-4d72-a4b3-8fc7171ac073"), UUID) Store(38, LENG) Store(0x5, CMDD) // EC_THM_GET_VAR Store(Arg0,INST) // Save instance ID Store(4,VLEN) // Variable is always DWORD here Store(Arg1, VUID) Store(Store(BUFF, \\_SB_.FFA0.FFAC), BUFF) If(LEqual(STAT,0x0) ) // Check FF-A successful? { Return (RVAL) } } Return (0x3) } // Arg0 Instance ID // Arg1 UUID of variable // Return (Status,Value) Method(SVAR,3,Serialized) { If(LEqual(\\_SB.FFA0.AVAL,One)) { Name(BUFF, Buffer(42){}) CreateByteField(BUFF,0,STAT) // Out – Status for req/rsp CreateByteField(BUFF,1,LENG) // In/Out – Bytes in req, updates bytes returned CreateField(BUFF,16,128,UUID) // UUID of service CreateByteField(BUFF,18,CMDD) // Command register CreateByteField(BUFF,19,INST) // Instance ID CreateWordField(BUFF,20,VLEN) // 16-bit variable length CreateField(BUFF,176,128,VUID) // UUID of variable to read CreateDwordField(BUFF,38,DVAL) // Data value CreateField(BUFF,208,32,RVAL) // Ouput Data Store(ToUUID("31f56da7-593c-4d72-a4b3-8fc7171ac073"), UUID) Store(42, LENG) Store(0x6, CMDD) // EC_THM_SET_VAR Store(Arg0,INST) // Save instance ID Store(4,VLEN) // Variable is always DWORD here Store(Arg1, VUID) Store(Arg2,DVAL) Store(Store(BUFF, \\_SB_.FFA0.FFAC), BUFF) If(LEqual(STAT,0x0) ) // Check FF-A successful? { Return (RVAL) } } Return (0x3) } // Arg0 GUID // 07ff6382-e29a-47c9-ac87-e79dad71dd82 - Input // d9b9b7f3-2a3e-4064-8841-cb13d317669e - Output // Arg1 Revision // Arg2 Function Index // Arg3 Function dependent Method(_DSM, 0x4, Serialized) { // Input Variable If(LEqual(ToUuid("07ff6382-e29a-47c9-ac87-e79dad71dd82"),Arg0)) { Switch(Arg2) { Case(0) { // We support function 0-3 Return(0xf) } Case(1) { Return(GVAR(1,ToUuid("ba17b567-c368-48d5-bc6f-a312a41583c1"))) // OnTemp } Case(2) { Return(GVAR(1,ToUuid("3a62688c-d95b-4d2d-bacc-90d7a5816bcd"))) // RampTemp } Case(3) { Return(GVAR(1,ToUuid("dcb758b1-f0fd-4ec7-b2c0-ef1e2a547b76"))) // MaxTemp } } Return(0x1) } // Output Variable If(LEqual(ToUuid("d9b9b7f3-2a3e-4064-8841-cb13d317669e"),Arg0)) { Switch(Arg2) { Case(0) { // We support function 0-3 Return(0xf) } Case(1) { Return(SVAR(1,ToUuid("ba17b567-c368-48d5-bc6f-a312a41583c1"),Arg3)) // OnTemp } Case(2) { Return(SVAR(1,ToUuid("3a62688c-d95b-4d2d-bacc-90d7a5816bcd"),Arg3)) // RampTemp } Case(3) { Return(SVAR(1,ToUuid("dcb758b1-f0fd-4ec7-b2c0-ef1e2a547b76"),Arg3)) // MaxTemp } } } Return (0x1) } } }
Call Flows for secure and non-secure Implementation
Depending on system requirements the ACPI calls may go directly to the EC or through secure world then through to EC.
When using non-secure interface the ACPI functions must define protocol level which is the Embedded controller for eSPI. For I2C/I3C or SPI interfaces the corresponding ACPI device must define the bus dependency and build the packet directly that is sent to the EC.
For secure communication all data is sent to the secure world via FF-A commands described in this document and the actual bus protocol and data sent to the EC is defined in the secure world in Hafnium. All support for FF-A is inboxed in the OS by default so EC communication will always work in any environment. However, FF-A is not supported in x86/x64 platforms so direct EC communication must be used on these platforms.
Non-Secure eSPI Access
This call flow assumes using Embedded controller definition with independent ACPI functions for MPTF support
Non-Secure eSPI READ
#![allow(unused)] fn main() { Device(EC0) { Name(_HID, EISAID("PNP0C09")) // ID for this EC // current resource description for this EC Name(_CRS, ResourceTemplate() { Memory32Fixed (ReadWrite, 0x100000, 0x10) // Used for simulated port access Memory32Fixed (ReadWrite, 0x100010, 0x10) // Interrupt defined for eSPI event signalling GpioInt(Edge, ActiveHigh, ExclusiveAndWake,PullUp 0,"\\_SB.GPI2"){43} }) Name(_GPE, 0) // GPE index for this EC // create EC's region and field for thermal support OperationRegion(EC0, EmbeddedControl, 0, 0xFF) Field(EC0, ByteAcc, Lock, Preserve) { MODE, 1, // thermal policy (quiet/perform) FAN, 1, // fan power (on/off) , 6, // reserved TMP, 16, // current temp AC0, 16, // active cooling temp (fan high) , 16, // reserved PSV, 16, // passive cooling temp HOT 16, // critical S4 temp CRT, 16 // critical temp BST1, 32, // Battery State BST2, 32, // Battery Present Rate BST3, 32, // Battery Remaining capacity BST4, 32, // Battery Present Voltage } Method (_BST) { Name (BSTD, Package (0x4) { \\_SB.PCI0.ISA0.EC0.BST1, // Battery State \\_SB.PCI0.ISA0.EC0.BST2, // Battery Present Rate \\_SB.PCI0.ISA0.EC0.BST3, // Battery Remaining Capacity \\_SB.PCI0.ISA0.EC0.BST4, // Battery Present Voltage }) Return(BSTD) } } }
sequenceDiagram OSPM->>ACPI: call _BST method ACPI->>ACPI: Map to EC0 fields in EC operation Region ACPI->>ACPI: EC0 accesses change to eSPI Peripheral accesses ACPI->>eSPI: Each field acccess changed to peripheral read/write ACPI->>ACPI: ACI handles SCI, port IO, MMIO, serialized ACPI->>ACPI: eSPI read/writes complete ACPI->>ACPI: Data is reorganized to _BST structure ACPI->>OSPM: Return _BST structure with status
Non-Secure eSPI Notifications
All interrupts are handled by the ACPI driver. When EC needs to send a notification event the GPIO is asserted and traps into IRQ. ACPI driver reads the EC_SC status register to determine if an SCI is pending. DPC callback calls and reads the EC_DATA port to determine the _Qxx event that is pending. Based on the event that is determined by ACPI the corresponding _Qxx event function is called.
#![allow(unused)] fn main() { Method (_Q07) { // Take action for event 7 Notify(\\_SB._LID, 0x80) } }
sequenceDiagram EC->>SCI ISR: EC asserts alert (IRQ) SCI ISR->>SCI DPC: Schedule DPC if EC_SC indicates SCI SCI DPC->>EC: Read EC_DATA to determine event EC->>SCI DPC: Send Qxx event SCI DPC->>ACPI: Call _Qxx function in EC0
Secure eSPI Access
The following flow assumes ARM platform using FF-A for secure calls. Note if you want to use the same EC firmware on both platforms with secure and non-secure access the EC_BAT_GET_BST in this case should be convert to a peripheral access with the same IO port and offset as non-secure definition.
Secure eSPI READ
#![allow(unused)] fn main() { Method (_BST) { // Check to make sure FFA is available and not unloaded If(LEqual(\\_SB.FFA0.AVAL,One)) { Name(BUFF, Buffer(32){}) // Create buffer for send/recv data CreateByteField(BUFF,0,STAT) // Out – Status for req/rsp CreateByteField(BUFF,1,LENG) // In/Out – Bytes in req, updates bytes returned CreateField(BUFF,16,128,UUID) // UUID of service CreateByteField(BUFF,18, CMDD) // In – First byte of command CreateDwordField(BUFF,19, BMA1) // In – Averaging Interval CreateField(BUFF,144,128,BSTD) // Out – 4 DWord BST data Store(ToUUID("25cb5207-ac36-427d-aaef-3aa78877d27e"), UUID) // Battery Store(42, LENG) Store(0x6, CMDD) // EC_BAT_GET_BST Store(Store(BUFF, \\_SB_.FFA0.FFAC), BUFF) If(LEqual(STAT,0x0) ) // Check FF-A successful? { Return (BMAD) } } Return(Zero) } }
sequenceDiagram OSPM->>ACPI: call_BST method ACPI->>FFA: Send EC_BAT_GET_BST_request FFA->>EC Service: Forward EC_BAT_GET_BST_request EC Service->>EC Service: Convert to eSPI peripheral read/write EC Service->>eSPI: send peripheral read/write access EC Service->>FFA: FFA_YIELD (as needed) FFA->>EC Service: FFA_RESUME (check for complete) eSPI->>EC Service: Return peripheral read data EC Service->>EC Service: Convert to EC_BAT_GET_BST response EC Service->>FFA: FFA response to original request FFA->>ACPI: return FFA status and _BST response ACPI->OSPM: return _BST structure
Secure eSPI Notification
When EC communication is done through Secure world we assert FIQ which is handled as eSPI interrupt. eSPI driver reads EC_SC and EC_DATA to retrieve the notification event details. On Non-secure implementation ACPI converts this to Qxx callback. On secure platform this is converted to a virtual ID and sent back to the OS via _NFY callback and a virtual ID.
#![allow(unused)] fn main() { Method(_NFY, 2, Serialized) { // Arg0 == UUID // Arg1 == Notify ID If(LEqual(ToUUID("25cb5207-ac36-427d-aaef-3aa78877d27e"),Arg0)) { If(LEqual(0x2,Arg1)) { Store(Arg1, \\_SB.ECT0.NEVT) Notify(\\_SB._LID, 0x80) } } } }
sequenceDiagram EC->>eSPI: EC asserts Alert (FIQ) eSPI->>EC: Read EC_SC to check for SCI eSPI->>EC: Read EC_DATA for SCI event EC->>eSPI: SCI Qxx event eSPI->>EC Service: Notification callback Qxx EC Service->>EC Service: Convert qxx to Virtual ID EC Service->>EC Nfy Service: Notify Virtual ID EC Nfy Service->>FFA:Send Physical ID FFA->>ACPI:Call_NFY with Virtual ID ACPI->>ACPI: Read SMEM notify details ACPI--)EC Service: Clear event (optional)