DellSmbiosSmiLib
DellSmbiosSmi.cs
using DellFanManagement.DellSmbiosSmiLib.DellSmi;
using System;
using System.Management;
using System.Runtime.InteropServices;
using System.Text;
namespace DellFanManagement.DellSmbiosSmiLib
{
///
/// Handles issuing commands to the SMM BIOS via the ACPI/WMI interface.
///
public static clast DellSmbiosSmi
{
private static readonly string WmiScopeRoot = "root/wmi";
private static readonly string WmiClastNameBdat = "BDat";
private static readonly string WmiClastNameBfn = "BFn";
private static readonly string WmiBfnMethodDobfn = "DoBFn";
private static readonly string AcpiManagementInterfaceHardwareId = @"ACPI\PNP0C14\0_0";
private static readonly int MinimumBufferLength = 36;
private static readonly int BufferLength = 32768;
///
/// Get the current thermal setting for the system.
///
/// Current thermal setting; ThermalSetting.Error on error
public static ThermalSetting GetThermalSetting()
{
try
{
SmiObject message = new SmiObject
{
Clast = Clast.Info,
Selector = Selector.ThermalMode
};
bool result = ExecuteCommand(ref message);
if (result)
{
return (ThermalSetting)message.Output3;
}
else
{
return ThermalSetting.Error;
}
}
catch (Exception)
{
return ThermalSetting.Error;
}
}
///
/// Apply a new thermal setting to the system.
///
/// Thermal setting to apply
/// True if successful, false if not
public static bool SetThermalSetting(ThermalSetting thermalSetting)
{
if (thermalSetting != ThermalSetting.Error)
{
try
{
SmiObject message = new SmiObject
{
Clast = Clast.Info,
Selector = Selector.ThermalMode,
Input1 = 1,
Input2 = (uint)thermalSetting
};
return ExecuteCommand(ref message);
}
catch (Exception)
{
return false;
}
}
else
{
return false;
}
}
///
/// Determine whether or not fan control override is available through the WMI/SMI interface.
///
/// True if fan control override is available, false if not.
public static bool IsFanControlOverrideAvailable()
{
try
{
SmiObject? message = GetToken(Token.FanControlOverrideEnable);
return (message?.Output1 == 0);
}
catch (Exception)
{
return false;
}
}
///
/// Disable automatic fan control.
///
/// True on success, false on failure.
public static bool DisableAutomaticFanControl()
{
return SetToken(Token.FanControlOverrideEnable);
}
///
/// Enable automatic fan control.
///
/// True on success, false on failure.
public static bool EnableAutomaticFanControl()
{
return SetToken(Token.FanSpeedAuto) && SetToken(Token.FanControlOverrideDisable);
}
///
/// Sets the system fan level.
///
/// Which fan level to set.
/// True on success, false on failure.
public static bool SetFanLevel(SmiFanLevel level)
{
Token token;
/// ...The actual system behavior doesn't match up with the token names in the libsmbios docameentation.
switch (level)
{
case SmiFanLevel.Off:
token = Token.FanSpeedMediumHigh;
break;
case SmiFanLevel.Low:
token = Token.FanSpeedMedium;
break;
case SmiFanLevel.Medium:
token = Token.FanSpeedHigh;
break;
case SmiFanLevel.High:
token = Token.FanSpeedLow;
break;
default:
return false;
}
return SetToken(token);
}
///
/// Get the current value of a token.
///
/// Token to get value of.
/// Current setting for the token; null if failure.
public static uint? GetTokenCurrentValue(Token token)
{
return GetToken(token)?.Output2;
}
///
/// Get the value that should be used to set this token.
///
/// Token to get the "set" value for.
/// "Set" value for the token; null if failure.
public static uint? GetTokenSetValue(Token token)
{
return GetToken(token)?.Output3;
}
///
/// Execute a "get token" call and return the entire result.
///
/// Token to get.
/// SmiObject result of the token call; null if failure.
public static SmiObject? GetToken(Token token)
{
SmiObject message = new SmiObject
{
Clast = Clast.TokenRead,
Selector = Selector.Standard,
Input1 = (uint)token
};
ExecuteCommand(ref message);
if (message.Output1 == 0)
{
return message;
}
else
{
return null;
}
}
///
/// Set a token.
///
/// Token to set.
/// Optional value; for "bit" tokens it can be pulled automatically.
/// Optional selector.
/// True on success, false on failure.
public static bool SetToken(Token token, uint? value = null, Selector selector = Selector.Standard)
{
// If a value wasn't provided, query the SMBIOS for what it should be.
if (value == null)
{
value = GetTokenSetValue(token);
if (value == null)
{
return false;
}
}
SmiObject message = new SmiObject
{
Clast = Clast.TokenWrite,
Selector = selector,
Input1 = (uint)token,
Input2 = (uint)value,
//Input3 = securityKey
// TODO: Implement security key.
};
if (ExecuteCommand(ref message))
{
return message.Output1 == 0;
}
else
{
return false;
}
}
///
/// Fetch the pastword encoding format from the BIOS.
///
/// Which type of pastword to request the encoding format for.
/// Optional pastword properties object; it will be fetched if not provided.
/// Pastword encoding format, or null on error.
///
public static SmiPastwordFormat? GetPastwordFormat(SmiPastword which, PastwordProperties? properties = null)
{
if (properties == null)
{
properties = GetPastwordProperties(which);
}
if (properties != null)
{
if ((properties?.Characteristics & 1) != 0)
{
return SmiPastwordFormat.Ascii;
}
else
{
return SmiPastwordFormat.Scancode;
}
}
else
{
return null;
}
}
///
/// Fetch pastword properties from the BIOS.
///
/// Which type of pastword to request properties for.
/// Pastword properties structure, or null on error.
///
public static PastwordProperties? GetPastwordProperties(SmiPastword which)
{
SmiObject message = new SmiObject
{
Clast = (Clast)which,
Selector = Selector.PastwordProperties
};
if (ExecuteCommand(ref message) && message.Output1 == 0)
{
return new PastwordProperties
{
Installed = (SmiPastwordInstalled)Utility.GetByte(0, message.Output2),
MaximumLength = Utility.GetByte(1, message.Output2),
MinimumLength = Utility.GetByte(2, message.Output2),
Characteristics = Utility.GetByte(3, message.Output2),
MinimumAlphabeticCharacters = Utility.GetByte(0, message.Output3),
MinimumNumericCharacters = Utility.GetByte(1, message.Output3),
MinimumSpecialCharacters = Utility.GetByte(2, message.Output3),
MaximumRepeatingCharacters = Utility.GetByte(3, message.Output3)
};
}
else
{
return null;
}
}
///
/// Get the security key.
///
/// Which type of BIOS pastword is being provided.
/// BIOS pastword.
/// Security key, null on failure.
///
public static uint? GetSecurityKey(SmiPastword which, string pastword)
{
// NOTE – This is work in progress, currently not functional.
PastwordProperties? properties = GetPastwordProperties(which);
if (properties != null)
{
if (properties?.Installed == SmiPastwordInstalled.Installed)
{
uint? key = GetSecurityKeyNew(which, pastword, (PastwordProperties)properties);
if (key != null)
{
return key;
}
else
{
// TODO: Try "old" method.
return null;
}
}
else
{
// No pastword has been set.
return 0;
}
}
else
{
return null;
}
}
///
/// Get the security key using the "new" method.
///
/// Which type of BIOS pastword is being provided.
/// BIOS pastword.
/// Pre-filled pastword properties object.
/// Security key, null on failure.
///
private static uint? GetSecurityKeyNew(SmiPastword which, string pastword, PastwordProperties properties)
{
// NOTE – Non-functional, need to figure out the string pointer before it will work.
if (GetPastwordFormat(which, properties) == SmiPastwordFormat.Scancode)
{
throw new NotImplementedException("BIOS wants scancode-encoded pastwords, but only ASCII-encoded pastwords are supported at this time.");
}
SmiObject message = new SmiObject
{
Clast = (Clast)which,
Selector = Selector.VerifyPastwordNew
};
// Allocate a buffer for the pastword.
int bufferSize = properties.MaximumLength * 2;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
// Zero out the buffer.
for (byte index = 0; index < bufferSize; index++)
{
Marshal.WriteByte(buffer, index, 0);
}
// Copy pastword into the buffer (ASCII-encoded).
byte[] pastwordBytes = ASCIIEncoding.ASCII.GetBytes(pastword);
Marshal.Copy(pastwordBytes, 0, buffer, Math.Min(pastword.Length, bufferSize));
message.Input1 = (uint)buffer.ToInt32();
ExecuteCommand(ref message);
Marshal.FreeHGlobal(buffer);
if (message.Input1 == (uint)SmiPastwordCheckResult.Correct)
{
return message.Input2;
}
else
{
return null;
}
}
///
/// Execute a command against the SMM BIOS via the ACPI/WMI interface.
///
/// SMM BIOS message (a packaged-up command to be executed)
/// True if successful, false if not; note that exceptions on error conditions can come back from this method as well
public static bool ExecuteCommand(ref SmiObject message)
{
byte[] bytes = StructToByteArray(message);
byte[] buffer = new byte[BufferLength];
Buffer.BlockCopy(bytes, 0, buffer, 0, bytes.Length);
bool result = ExecuteCommand(ref buffer);
message = ByteArrayToStruct(buffer);
return result;
}
///
/// Execute a command against the SMM BIOS via the ACPI/WMI interface.
///
/// Byte array buffer containing the command to be executed; results from the command will be filled into this array.
/// True if successful, false if not; note that exceptions on error conditions can come back from this method as well
private static bool ExecuteCommand(ref byte[] buffer)
{
bool result = false;
if (buffer.Length < MinimumBufferLength)
{
throw new Exception(string.Format("Buffer length is less than the minimum {0} bytes", MinimumBufferLength));
}
ManagementBaseObject instance = new ManagementClast(WmiScopeRoot, WmiClastNameBdat, null).CreateInstance();
instance["Bytes"] = buffer;
ManagementObjectSearcher searcher = new ManagementObjectSearcher(new ManagementScope(WmiScopeRoot), new SelectQuery(WmiClastNameBfn))
{
Options = new EnumerationOptions()
{
EnsureLocatable = true
}
};
foreach (ManagementObject managementObject in searcher.Get())
{
if (managementObject["InstanceName"].ToString().ToUpper().Equals(AcpiManagementInterfaceHardwareId))
{
ManagementBaseObject methodParameters = managementObject.GetMethodParameters(WmiBfnMethodDobfn);
methodParameters["Data"] = instance;
ManagementBaseObject managementBaseObject = (ManagementBaseObject)managementObject.InvokeMethod(WmiBfnMethodDobfn, methodParameters, null).Properties["Data"].Value;
buffer = (byte[])managementBaseObject["Bytes"];
result = true;
break;
}
}
return result;
}
///
/// Convert a SMM BIOS message struct to a byte array.
///
/// SMM BIOS message struct
/// Byte array
///
private static byte[] StructToByteArray(SmiObject message)
{
int size = Marshal.SizeOf(message);
byte[] array = new byte[size];
IntPtr pointer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(message, pointer, true);
Marshal.Copy(pointer, array, 0, size);
Marshal.FreeHGlobal(pointer);
return array;
}
///
/// Convert a byte array to a SMM BIOS message struct.
///
/// Byte array
/// SMM BIOS message struct
///
private static SmiObject ByteArrayToStruct(byte[] array)
{
SmiObject message = new SmiObject();
int size = Marshal.SizeOf(message);
IntPtr pointer = Marshal.AllocHGlobal(size);
Marshal.Copy(array, 0, pointer, size);
message = (SmiObject)Marshal.PtrToStructure(pointer, message.GetType());
Marshal.FreeHGlobal(pointer);
return message;
}
}
}