Sensors on Framework Laptops (#1681)
* Begin CrOS implementation for Framework Laptops * CrOS: Talk directly to the EC, instead of through ACPI * Add a few Framework models * Remove early Framework models These have a different EC, and don't currently work * Migrate CrOS ECs to PawnIO * Implement EcCommand and read sensor names * Add more Framework models * Add some error handling * Misc --------- Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
This commit is contained in:
co-authored by
PhyxionNL
parent
0ed17c77ce
commit
b02fc4c4cf
@@ -52,6 +52,8 @@ internal class Identification
|
||||
return Manufacturer.FIC;
|
||||
case var _ when name.Equals("Foxconn", StringComparison.OrdinalIgnoreCase):
|
||||
return Manufacturer.Foxconn;
|
||||
case var _ when name.StartsWith("Framework", StringComparison.OrdinalIgnoreCase):
|
||||
return Manufacturer.Framework;
|
||||
case var _ when name.StartsWith("Fujitsu", StringComparison.OrdinalIgnoreCase):
|
||||
return Manufacturer.Fujitsu;
|
||||
case var _ when name.StartsWith("Gigabyte", StringComparison.OrdinalIgnoreCase):
|
||||
@@ -726,6 +728,64 @@ internal class Identification
|
||||
return Model.PROART_B760_CREATOR_D4;
|
||||
case var _ when name.Equals("TUF GAMING B450-PLUS II", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.TUF_GAMING_B450_PLUS_II;
|
||||
case var _ when name.Equals("FRANBMCP03", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANBMCP03;
|
||||
case var _ when name.Equals("FRANBMCP06", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANBMCP06;
|
||||
case var _ when name.Equals("FRANBMCP08", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANBMCP08;
|
||||
case var _ when name.Equals("FRANBMCP0A", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANBMCP0A;
|
||||
case var _ when name.Equals("FRANBMCP0B", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANBMCP0B;
|
||||
case var _ when name.Equals("FRANBMCP0C", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANBMCP0C;
|
||||
case var _ when name.Equals("FRANGACP04", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANGACP04;
|
||||
case var _ when name.Equals("FRANGACP06", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANGACP06;
|
||||
case var _ when name.Equals("FRANGACP08", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANGACP08;
|
||||
case var _ when name.Equals("FRANMACP04", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMACP04;
|
||||
case var _ when name.Equals("FRANMACP06", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMACP06;
|
||||
case var _ when name.Equals("FRANMACP08", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMACP08;
|
||||
case var _ when name.Equals("FRANMCCP04", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMCCP04;
|
||||
case var _ when name.Equals("FRANMCCP06", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMCCP06;
|
||||
case var _ when name.Equals("FRANMCCP07", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMCCP07;
|
||||
case var _ when name.Equals("FRANMDCP05", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMDCP05;
|
||||
case var _ when name.Equals("FRANMDCP07", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMDCP07;
|
||||
case var _ when name.Equals("FRANMECP02", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMECP02;
|
||||
case var _ when name.Equals("FRANMECP05", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMECP05;
|
||||
case var _ when name.Equals("FRANMECP06", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMECP06;
|
||||
case var _ when name.Equals("FRANMZCP07", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMZCP07;
|
||||
case var _ when name.Equals("FRANMZCP09", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMZCP09;
|
||||
case var _ when name.Equals("FRANMFCP02", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMFCP02;
|
||||
case var _ when name.Equals("FRANMFCP06", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMFCP06;
|
||||
case var _ when name.Equals("FRAPMACP03", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRAPMACP03;
|
||||
case var _ when name.Equals("FRAPMACP05", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRAPMACP05;
|
||||
case var _ when name.Equals("FRANMGCP05", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMGCP05;
|
||||
case var _ when name.Equals("FRANMGCP07", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMGCP07;
|
||||
case var _ when name.Equals("FRANMGCP09", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.FRANMGCP09;
|
||||
case var _ when name.Equals("Base Board Product Name", StringComparison.OrdinalIgnoreCase):
|
||||
case var _ when name.Equals("To be filled by O.E.M.", StringComparison.OrdinalIgnoreCase):
|
||||
return Model.Unknown;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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.Collections.Generic;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
|
||||
|
||||
public class ChromeOSEmbeddedController : EmbeddedController
|
||||
{
|
||||
private const byte EC_FAN_SPEED_ENTRIES = 4;
|
||||
private const ushort EC_FAN_SPEED_NOT_PRESENT = 0xffff;
|
||||
private const byte EC_MEMMAP_FAN = 0x10;
|
||||
private const byte EC_MEMMAP_TEMP_SENSOR = 0x00;
|
||||
private const byte EC_MEMMAP_TEMP_SENSOR_B = 0x18;
|
||||
private const byte EC_TEMP_SENSOR_B_ENTRIES = 8;
|
||||
private const byte EC_TEMP_SENSOR_ENTRIES = 16;
|
||||
private const byte EC_TEMP_SENSOR_NOT_PRESENT = 0xff;
|
||||
|
||||
/*
|
||||
* The offset of temperature value stored in mapped memory. This allows
|
||||
* reporting a temperature range of 200K to 454K = -73C to 181C.
|
||||
*/
|
||||
private const byte EC_TEMP_SENSOR_OFFSET = 200;
|
||||
|
||||
public ChromeOSEmbeddedController(IEnumerable<EmbeddedControllerSource> sources, ISettings settings) : base(sources, settings)
|
||||
{ }
|
||||
|
||||
public static ChromeOSEmbeddedController Create(ISettings settings)
|
||||
{
|
||||
List<EmbeddedControllerSource> sources = [];
|
||||
|
||||
using ChromeOSEmbeddedControllerIO embeddedControllerIO = new();
|
||||
|
||||
// Copy the first 0x20 bytes of the EC memory map
|
||||
byte[] data = embeddedControllerIO.ReadMemmap(0x00, 0x20);
|
||||
|
||||
for (int i = 0; i < EC_TEMP_SENSOR_ENTRIES; i++)
|
||||
{
|
||||
byte temp = data[EC_MEMMAP_TEMP_SENSOR + i];
|
||||
if (temp == EC_TEMP_SENSOR_NOT_PRESENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string sensorName = embeddedControllerIO.TempSensorGetName((byte)i);
|
||||
|
||||
sources.Add(new EmbeddedControllerSource(sensorName,
|
||||
SensorType.Temperature,
|
||||
(ushort)(EC_MEMMAP_TEMP_SENSOR + i),
|
||||
offset: EC_TEMP_SENSOR_OFFSET - 273,
|
||||
blank: EC_TEMP_SENSOR_NOT_PRESENT));
|
||||
}
|
||||
|
||||
for (int i = 0; i < EC_FAN_SPEED_ENTRIES; i++)
|
||||
{
|
||||
ushort fan = (ushort)(data[EC_MEMMAP_FAN + i * 2] | (data[EC_MEMMAP_FAN + i * 2 + 1] << 8));
|
||||
if (fan == EC_FAN_SPEED_NOT_PRESENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
sources.Add(new EmbeddedControllerSource("Fan " + (i + 1),
|
||||
SensorType.Fan,
|
||||
(ushort)(EC_MEMMAP_FAN + i * 2),
|
||||
2,
|
||||
blank: EC_FAN_SPEED_NOT_PRESENT,
|
||||
isLittleEndian: true));
|
||||
}
|
||||
|
||||
for (int i = 0; i < EC_TEMP_SENSOR_B_ENTRIES; i++)
|
||||
{
|
||||
byte temp = data[EC_MEMMAP_TEMP_SENSOR_B + i];
|
||||
if (temp == EC_TEMP_SENSOR_NOT_PRESENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string sensorName = embeddedControllerIO.TempSensorGetName((byte)(i + EC_TEMP_SENSOR_ENTRIES));
|
||||
|
||||
sources.Add(new EmbeddedControllerSource(sensorName,
|
||||
SensorType.Temperature,
|
||||
(ushort)(EC_MEMMAP_TEMP_SENSOR_B + i),
|
||||
offset: EC_TEMP_SENSOR_OFFSET - 273,
|
||||
blank: EC_TEMP_SENSOR_NOT_PRESENT));
|
||||
}
|
||||
|
||||
return new ChromeOSEmbeddedController(sources, settings);
|
||||
}
|
||||
|
||||
protected override IEmbeddedControllerIO AcquireIOInterface()
|
||||
{
|
||||
return new ChromeOSEmbeddedControllerIO();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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.Text;
|
||||
using LibreHardwareMonitor.PawnIo;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
|
||||
|
||||
public class ChromeOSEmbeddedControllerIO : IEmbeddedControllerIO
|
||||
{
|
||||
private const short EC_CMD_TEMP_SENSOR_GET_INFO = 0x0070;
|
||||
|
||||
private readonly LpcCrOSEc _pawnModule;
|
||||
private bool _disposed;
|
||||
|
||||
public ChromeOSEmbeddedControllerIO()
|
||||
{
|
||||
_pawnModule = new LpcCrOSEc();
|
||||
|
||||
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");
|
||||
|
||||
for (int i = 0; i < registers.Length; i++)
|
||||
{
|
||||
data[i] = ReadMemmap((byte)registers[i], 1)[0];
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ReadMemmap(byte offset, byte bytes)
|
||||
{
|
||||
return _pawnModule.ReadMemmap(offset, bytes);
|
||||
}
|
||||
|
||||
public string TempSensorGetName(byte index)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] resp = _pawnModule.EcCommand(0, EC_CMD_TEMP_SENSOR_GET_INFO, 1, 33, [index]);
|
||||
//byte sensorType = resp[33];
|
||||
return Encoding.ASCII.GetString(resp, 0, 32).TrimEnd('\0');
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
return "Temp " + index;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
Mutexes.ReleaseEc();
|
||||
}
|
||||
}
|
||||
|
||||
public class BusMutexLockingFailedException : EmbeddedController.IOException
|
||||
{
|
||||
public BusMutexLockingFailedException()
|
||||
: base("could not lock ISA bus mutex")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,48 @@ public abstract class EmbeddedController : Hardware
|
||||
ECSensor.TempCPUPackage,
|
||||
ECSensor.TempMB,
|
||||
ECSensor.TempVrm),
|
||||
new(Model.FRANMDCP05,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMDCP07,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMECP02,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMECP05,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMECP06,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMZCP07,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMZCP09,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMFCP02,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMFCP06,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRAPMACP03,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRAPMACP05,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMGCP05,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMGCP07,
|
||||
BoardFamily.CrOS,
|
||||
null),
|
||||
new(Model.FRANMGCP09,
|
||||
BoardFamily.CrOS,
|
||||
null)
|
||||
};
|
||||
|
||||
private static readonly Dictionary<BoardFamily, Dictionary<ECSensor, EmbeddedControllerSource>> _knownSensors = new()
|
||||
@@ -454,6 +496,12 @@ public abstract class EmbeddedController : Hardware
|
||||
}
|
||||
|
||||
BoardInfo board = boards[0];
|
||||
|
||||
if (board.Family == BoardFamily.CrOS)
|
||||
{
|
||||
return ChromeOSEmbeddedController.Create(settings);
|
||||
}
|
||||
|
||||
IEnumerable<EmbeddedControllerSource> sources = board.Sensors.Select(ecs => _knownSensors[board.Family][ecs]);
|
||||
|
||||
return Environment.OSVersion.Platform switch
|
||||
@@ -474,16 +522,19 @@ public abstract class EmbeddedController : Hardware
|
||||
int readRegister = 0;
|
||||
for (int si = 0; si < _sensors.Count; ++si)
|
||||
{
|
||||
int littleEndian = _sources[si].IsLittleEndian ? 1 : 0;
|
||||
int bigEndian = _sources[si].IsLittleEndian ? 0 : 1;
|
||||
|
||||
int val = _sources[si].Size switch
|
||||
{
|
||||
1 => _sources[si].Type switch { SensorType.Temperature => unchecked((sbyte)_data[readRegister]), _ => _data[readRegister] },
|
||||
2 => unchecked((short)((_data[readRegister] << 8) + _data[readRegister + 1])),
|
||||
2 => unchecked((short)((_data[readRegister + littleEndian] << 8) + _data[readRegister + bigEndian])),
|
||||
_ => 0
|
||||
};
|
||||
|
||||
readRegister += _sources[si].Size;
|
||||
|
||||
_sensors[si].Value = val != _sources[si].Blank ? val * _sources[si].Factor : null;
|
||||
_sensors[si].Value = val != _sources[si].Blank ? (val * _sources[si].Factor) + _sources[si].Offset : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,7 +663,8 @@ public abstract class EmbeddedController : Hardware
|
||||
Intel300,
|
||||
Intel400,
|
||||
Intel600,
|
||||
Intel700
|
||||
Intel700,
|
||||
CrOS
|
||||
}
|
||||
|
||||
private struct BoardInfo
|
||||
|
||||
@@ -5,27 +5,21 @@
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
|
||||
|
||||
public class EmbeddedControllerSource
|
||||
public class EmbeddedControllerSource(string name, SensorType type, ushort register, byte size = 1, float factor = 1.0f, float offset = 0.0f, int blank = int.MaxValue, bool isLittleEndian = false)
|
||||
{
|
||||
public EmbeddedControllerSource(string name, SensorType type, ushort register, byte size = 1, float factor = 1.0f, int blank = int.MaxValue)
|
||||
{
|
||||
Name = name;
|
||||
public int Blank { get; } = blank;
|
||||
|
||||
Register = register;
|
||||
Size = size;
|
||||
Type = type;
|
||||
Factor = factor;
|
||||
Blank = blank;
|
||||
}
|
||||
public float Factor { get; } = factor;
|
||||
|
||||
public string Name { get; }
|
||||
public ushort Register { get; }
|
||||
public byte Size { get; }
|
||||
public float Factor { get; }
|
||||
public bool IsLittleEndian { get; } = isLittleEndian;
|
||||
|
||||
public int Blank { get; }
|
||||
public string Name { get; } = name;
|
||||
|
||||
public EmbeddedControllerReader Reader { get; }
|
||||
public float Offset { get; } = offset;
|
||||
|
||||
public SensorType Type { get; }
|
||||
}
|
||||
public ushort Register { get; } = register;
|
||||
|
||||
public byte Size { get; } = size;
|
||||
|
||||
public SensorType Type { get; } = type;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public enum Manufacturer
|
||||
EVGA,
|
||||
FIC,
|
||||
Foxconn,
|
||||
Framework,
|
||||
Fujitsu,
|
||||
Gateway,
|
||||
Gigabyte,
|
||||
|
||||
@@ -314,6 +314,37 @@ public enum Model
|
||||
//Supermicro
|
||||
X11SWN_E,
|
||||
|
||||
// Framework
|
||||
FRANBMCP03, // Intel Core i5-1135G7
|
||||
FRANBMCP06, // Intel Core i7-1165G7
|
||||
FRANBMCP08, // Intel Core i7-1185G7
|
||||
FRANBMCP0A, // Intel Core i5-1135G7
|
||||
FRANBMCP0B, // Intel Core i7-1165G7
|
||||
FRANBMCP0C, // Intel Core i7-1185G7
|
||||
FRANGACP04, // Intel Core i5-1240P
|
||||
FRANGACP06, // Intel Core i7-1260P
|
||||
FRANGACP08, // Intel Core i7-1280P
|
||||
FRANMACP04, // Intel Core i5-1240P
|
||||
FRANMACP06, // Intel Core i7-1260P
|
||||
FRANMACP08, // Intel Core i7-1280P
|
||||
FRANMCCP04, // Intel Core i5-1340P
|
||||
FRANMCCP06, // Intel Core i7-1360P
|
||||
FRANMCCP07, // Intel Core i7-1370P
|
||||
FRANMDCP05, // FW13 AMD Ryzen 5 7640U
|
||||
FRANMDCP07, // FW13 AMD Ryzen 7 7840U
|
||||
FRANMECP02, // FW13 Intel Core Ultra 5 125H
|
||||
FRANMECP05, // FW13 Intel Core Ultra 7 155H
|
||||
FRANMECP06, // FW13 Intel Core Ultra 7 165H
|
||||
FRANMZCP07, // FW16 AMD Ryzen 7 7840HS
|
||||
FRANMZCP09, // FW16 AMD Ryzen 9 7940HS
|
||||
FRANMFCP02, // Desktop AMD RYZEN AI MAX 385
|
||||
FRANMFCP06, // Desktop AMD RYZEN AI MAX+ 395
|
||||
FRAPMACP03, // FW12 Intel Core i3-1315U
|
||||
FRAPMACP05, // FW12 Intel Core i5-1334U
|
||||
FRANMGCP05, // FW13 AMD Ryzen AI 5 340
|
||||
FRANMGCP07, // FW13 AMD Ryzen AI 7 350
|
||||
FRANMGCP09, // FW13 AMD Ryzen AI 9 HX 370
|
||||
|
||||
// Unknown
|
||||
Unknown
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace LibreHardwareMonitor.PawnIo;
|
||||
|
||||
public class LpcCrOSEc
|
||||
{
|
||||
private readonly PawnIo _pawnIO = PawnIo.LoadModuleFromResource(typeof(LpcCrOSEc).Assembly, $"{nameof(LibreHardwareMonitor)}.Resources.PawnIo.LpcCrOSEC.bin");
|
||||
|
||||
public byte[] EcCommand(int version, int command, int outsize, int insize, byte[] data)
|
||||
{
|
||||
long[] inArray = new long[38];
|
||||
inArray[0] = version;
|
||||
inArray[1] = command;
|
||||
inArray[2] = outsize;
|
||||
inArray[3] = insize;
|
||||
|
||||
// Start packing data into inArray at the 4th long (8 bytes)
|
||||
Buffer.BlockCopy(data, 0, inArray, 4 * 8, data.Length);
|
||||
|
||||
long[] outArray = _pawnIO.Execute("ioctl_ec_command", inArray, 1 + (int)Math.Ceiling(insize / 8.0));
|
||||
if (outArray[0] < 0)
|
||||
{
|
||||
throw new Exception("EC returned error code " + -outArray[0]);
|
||||
}
|
||||
|
||||
byte[] retArray = new byte[insize];
|
||||
// Unpack the data skipping the first long
|
||||
Buffer.BlockCopy(outArray, 8, retArray, 0, insize);
|
||||
return retArray;
|
||||
}
|
||||
|
||||
public byte[] ReadMemmap(byte offset, byte bytes)
|
||||
{
|
||||
long[] inArray = [offset, bytes];
|
||||
long[] outArray = _pawnIO.Execute("ioctl_ec_readmem", inArray, (int)Math.Ceiling(bytes / 8.0));
|
||||
byte[] retArray = new byte[bytes];
|
||||
Buffer.BlockCopy(outArray, 0, retArray, 0, bytes);
|
||||
return retArray;
|
||||
}
|
||||
|
||||
public void Close() => _pawnIO.Close();
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user