/*
* dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
*
* Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org>
*
* Hwmon integration:
* Copyright (C) 2011 Jean Delvare <jdelvare@suse.de>
* Copyright (C) 2013, 2014 Guenter Roeck <linux@roeck-us.net>
* Copyright (C) 2014, 2015 Pali Rohár <pali.rohar@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/dmi.h>
#include <linux/capability.h>
#include <linux/mutex.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/ctype.h>
#include <linux/i8k.h>
#define I8K_SMM_FN_STATUS 0x0025
#define I8K_SMM_POWER_STATUS 0x0069
#define I8K_SMM_SET_FAN 0x01a3
#define I8K_SMM_GET_FAN 0x00a3
#define I8K_SMM_GET_SPEED 0x02a3
#define I8K_SMM_GET_FAN_TYPE 0x03a3
#define I8K_SMM_GET_NOM_SPEED 0x04a3
#define I8K_SMM_GET_TEMP 0x10a3
#define I8K_SMM_GET_TEMP_TYPE 0x11a3
#define I8K_SMM_GET_DELL_SIG1 0xfea3
#define I8K_SMM_GET_DELL_SIG2 0xffa3
#define I8K_FAN_MULT 30
#define I8K_FAN_MAX_RPM 30000
#define I8K_MAX_TEMP 127
#define I8K_FN_NONE 0x00
#define I8K_FN_UP 0x01
#define I8K_FN_DOWN 0x02
#define I8K_FN_MUTE 0x04
#define I8K_FN_MASK 0x07
#define I8K_FN_SHIFT 8
#define I8K_POWER_AC 0x05
#define I8K_POWER_BATTERY 0x01
static DEFINE_MUTEX(i8k_mutex);
static char bios_version[4];
static char bios_machineid[16];
static struct device *i8k_hwmon_dev;
static u32 i8k_hwmon_flags;
static uint i8k_fan_mult = I8K_FAN_MULT;
static uint i8k_pwm_mult;
static uint i8k_fan_max = I8K_FAN_HIGH;
static bool disallow_fan_type_call;
#define I8K_HWMON_HAVE_TEMP1 (1 << 0)
#define I8K_HWMON_HAVE_TEMP2 (1 << 1)
#define I8K_HWMON_HAVE_TEMP3 (1 << 2)
#define I8K_HWMON_HAVE_TEMP4 (1 << 3)
#define I8K_HWMON_HAVE_FAN1 (1 << 4)
#define I8K_HWMON_HAVE_FAN2 (1 << 5)
#define I8K_HWMON_HAVE_FAN3 (1 << 6)
MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("i8k");
static bool force;
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported models");
static bool ignore_dmi;
module_param(ignore_dmi, bool, 0);
MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
#if IS_ENABLED(CONFIG_I8K)
static bool restricted = true;
module_param(restricted, bool, 0);
MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)");
static bool power_status;
module_param(power_status, bool, 0600);
MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)");
#endif
static uint fan_mult;
module_param(fan_mult, uint, 0);
MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
static uint fan_max;
module_param(fan_max, uint, 0);
MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
struct smm_regs {
unsigned int eax;
unsigned int ebx __packed;
unsigned int ecx __packed;
unsigned int edx __packed;
unsigned int esi __packed;
unsigned int edi __packed;
};
static inline const char *i8k_get_dmi_data(int field)
{
const char *dmi_data = dmi_get_system_info(field);
return dmi_data && *dmi_data ? dmi_data : "?";
}
/*
* Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
*/
static int i8k_smm(struct smm_regs *regs)
{
int rc;
int eax = regs->eax;
cpumask_var_t old_mask;
#ifdef DEBUG
int ebx = regs->ebx;
unsigned long duration;
ktime_t calltime, delta, rettime;
calltime = ktime_get();
#endif
/* SMM requires CPU 0 */
if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
return -ENOMEM;
cpumask_copy(old_mask, ¤t->cpus_allowed);
rc = set_cpus_allowed_ptr(current, cpumask_of(0));
if (rc)
goto out;