Files
WiktorandPhyxionNL 2423b1b7f1 Fix ASUS Z170 EC temperature read returning zero values (#2153)
* Fix ASUS Z170 EC temperature read returning zero values
Adjust EC read logic so temperature registers (PCH, T_SENSOR, VRM)
return valid values on ASUS Z170 boards in user-mode.
Verified with RWEverything and HWiNFO.

* merge back to inter100,
Use IBF clearing fallback when OBF unsupported

* Fix formatting of CrOS enum entry

* Update WindowsEmbeddedControllerIO.cs

---------

Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
2026-02-01 15:40:16 +01:00

271 lines
6.8 KiB
C#

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Diagnostics;
using System.Threading;
using LibreHardwareMonitor.PawnIo;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
/// <summary>
/// An unsafe but universal implementation for the ACPI Embedded Controller IO interface for Windows
/// </summary>
/// <remarks>
/// It is unsafe because of possible race condition between this application and the PC firmware when
/// writing to the EC registers. For a safe approach ACPI/WMI methods have to be used, but those are
/// different for each motherboard model.
/// </remarks>
public class WindowsEmbeddedControllerIO : IEmbeddedControllerIO
{
private const int FailuresBeforeSkip = 20;
private const int MaxRetries = 5;
// implementation
private const int WaitSpins = 50;
private readonly LpcAcpiEc _pawnModule;
private bool _disposed;
private int _waitReadFailures;
public WindowsEmbeddedControllerIO()
{
_pawnModule = new LpcAcpiEc();
if (!Mutexes.WaitEc(10))
{
throw new BusMutexLockingFailedException();
}
}
public void Read(ushort[] registers, byte[] data)
{
Trace.Assert(registers.Length <= data.Length,
"data buffer length has to be greater or equal to the registers array length");
byte bank = 0;
byte prevBank = SwitchBank(bank);
// oops... somebody else is working with the EC too
Trace.WriteLineIf(prevBank != 0, "Concurrent access to the ACPI EC detected.\nRace condition possible.");
// read registers minimizing bank switches.
for (int i = 0; i < registers.Length; i++)
{
byte regBank = (byte)(registers[i] >> 8);
byte regIndex = (byte)(registers[i] & 0xFF);
// registers are sorted by bank
if (regBank > bank)
{
bank = SwitchBank(regBank);
}
data[i] = ReadByte(regIndex);
}
SwitchBank(prevBank);
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
Mutexes.ReleaseEc();
_pawnModule.Close();
}
}
private byte ReadByte(byte register)
{
return ReadLoop<byte>(register, ReadByteOp);
}
private void WriteByte(byte register, byte value)
{
WriteLoop(register, value, WriteByteOp);
}
private byte SwitchBank(byte bank)
{
byte previous = ReadByte(0xFF);
WriteByte(0xFF, bank);
return previous;
}
private TResult ReadLoop<TResult>(byte register, ReadOp<TResult> op) where TResult : new()
{
TResult result = new();
for (int i = 0; i < MaxRetries; i++)
{
if (op(register, out result))
{
return result;
}
}
return result;
}
private void WriteLoop<TValue>(byte register, TValue value, WriteOp<TValue> op)
{
for (int i = 0; i < MaxRetries; i++)
{
if (op(register, value))
{
return;
}
}
}
private bool WaitForStatus(Status status, bool isSet)
{
for (int i = 0; i < WaitSpins; i++)
{
byte value = ReadIOPort(Port.Command);
if (((byte)status & (!isSet ? value : (byte)~value)) == 0)
{
return true;
}
Thread.Sleep(1);
}
return false;
}
private bool WaitRead()
{
if (_waitReadFailures > FailuresBeforeSkip)
{
return true;
}
// Try OBF with reduced timeout
for (int i = 0; i < MaxRetries; i++)
{
byte status = ReadIOPort(Port.Command);
if ((status & (byte)Status.OutputBufferFull) != 0)
{
_waitReadFailures = 0;
return true;
}
Thread.Sleep(1);
}
// ASUS workaround: Wait for IBF to clear instead of OBF
// Testing on Z170 Pro Gaming shows IBF clears in 1-3ms when data is ready
for (int i = 0; i < WaitSpins; i++)
{
byte status = ReadIOPort(Port.Command);
if ((status & (byte)Status.InputBufferFull) == 0)
{
_waitReadFailures = 0;
return true;
}
Thread.Sleep(1);
}
_waitReadFailures++;
return false;
}
private bool WaitWrite()
{
return WaitForStatus(Status.InputBufferFull, false);
}
private byte ReadIOPort(Port port)
{
return _pawnModule.ReadPort((byte)port);
}
private void WriteIOPort(Port port, byte datum)
{
_pawnModule.WritePort((byte)port, datum);
}
protected bool ReadByteOp(byte register, out byte value)
{
if (WaitWrite())
{
WriteIOPort(Port.Command, (byte)Command.Read);
if (WaitWrite())
{
WriteIOPort(Port.Data, register);
if (WaitWrite() && WaitRead())
{
value = ReadIOPort(Port.Data);
return true;
}
}
}
value = 0;
return false;
}
protected bool WriteByteOp(byte register, byte value)
{
if (WaitWrite())
{
WriteIOPort(Port.Command, (byte)Command.Write);
if (WaitWrite())
{
WriteIOPort(Port.Data, register);
if (WaitWrite())
{
WriteIOPort(Port.Data, value);
return true;
}
}
}
return false;
}
public class BusMutexLockingFailedException : EmbeddedController.IOException
{
public BusMutexLockingFailedException() : base("could not lock ISA bus mutex")
{ }
}
private delegate bool ReadOp<TParam>(byte register, out TParam p);
private delegate bool WriteOp<in TParam>(byte register, TParam p);
// see the ACPI specification chapter 12
private enum Port : byte
{
Command = 0x66,
Data = 0x62
}
private enum Command : byte
{
Read = 0x80, // RD_EC
Write = 0x81, // WR_EC
BurstEnable = 0x82, // BE_EC
BurstDisable = 0x83, // BD_EC
Query = 0x84 // QR_EC
}
private enum Status : byte
{
OutputBufferFull = 0x01, // EC_OBF
InputBufferFull = 0x02, // EC_IBF
Command = 0x08, // CMD
BurstMode = 0x10, // BURST
SciEventPending = 0x20, // SCI_EVT
SmiEventPending = 0x40 // SMI_EVT
}
}