csharp/AaronKelley/DellFanManagement/DellSmbiosSmiLib/DellSmbiosSmi.cs

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;
        }
    }
}