162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Common functions for kernel modules using Dell SMBIOS 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) Red Hat <mjg@redhat.com> 662306a36Sopenharmony_ci * Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com> 762306a36Sopenharmony_ci * Copyright (c) 2014 Pali Rohár <pali@kernel.org> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Based on documentation in the libsmbios package: 1062306a36Sopenharmony_ci * Copyright (C) 2005-2014 Dell Inc. 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/capability.h> 1762306a36Sopenharmony_ci#include <linux/dmi.h> 1862306a36Sopenharmony_ci#include <linux/err.h> 1962306a36Sopenharmony_ci#include <linux/mutex.h> 2062306a36Sopenharmony_ci#include <linux/platform_device.h> 2162306a36Sopenharmony_ci#include <linux/slab.h> 2262306a36Sopenharmony_ci#include "dell-smbios.h" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistatic u32 da_supported_commands; 2562306a36Sopenharmony_cistatic int da_num_tokens; 2662306a36Sopenharmony_cistatic struct platform_device *platform_device; 2762306a36Sopenharmony_cistatic struct calling_interface_token *da_tokens; 2862306a36Sopenharmony_cistatic struct device_attribute *token_location_attrs; 2962306a36Sopenharmony_cistatic struct device_attribute *token_value_attrs; 3062306a36Sopenharmony_cistatic struct attribute **token_attrs; 3162306a36Sopenharmony_cistatic DEFINE_MUTEX(smbios_mutex); 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct smbios_device { 3462306a36Sopenharmony_ci struct list_head list; 3562306a36Sopenharmony_ci struct device *device; 3662306a36Sopenharmony_ci int (*call_fn)(struct calling_interface_buffer *arg); 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct smbios_call { 4062306a36Sopenharmony_ci u32 need_capability; 4162306a36Sopenharmony_ci int cmd_class; 4262306a36Sopenharmony_ci int cmd_select; 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* calls that are whitelisted for given capabilities */ 4662306a36Sopenharmony_cistatic struct smbios_call call_whitelist[] = { 4762306a36Sopenharmony_ci /* generally tokens are allowed, but may be further filtered or 4862306a36Sopenharmony_ci * restricted by token blacklist or whitelist 4962306a36Sopenharmony_ci */ 5062306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_STD}, 5162306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_AC}, 5262306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_BAT}, 5362306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD}, 5462306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_AC}, 5562306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT}, 5662306a36Sopenharmony_ci /* used by userspace: fwupdate */ 5762306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_ADMIN_PROP, SELECT_ADMIN_PROP}, 5862306a36Sopenharmony_ci /* used by userspace: fwupd */ 5962306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_INFO, SELECT_DOCK}, 6062306a36Sopenharmony_ci {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE, SELECT_FLASH_INTERFACE}, 6162306a36Sopenharmony_ci}; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/* calls that are explicitly blacklisted */ 6462306a36Sopenharmony_cistatic struct smbios_call call_blacklist[] = { 6562306a36Sopenharmony_ci {0x0000, 1, 7}, /* manufacturing use */ 6662306a36Sopenharmony_ci {0x0000, 6, 5}, /* manufacturing use */ 6762306a36Sopenharmony_ci {0x0000, 11, 3}, /* write once */ 6862306a36Sopenharmony_ci {0x0000, 11, 7}, /* write once */ 6962306a36Sopenharmony_ci {0x0000, 11, 11}, /* write once */ 7062306a36Sopenharmony_ci {0x0000, 19, -1}, /* diagnostics */ 7162306a36Sopenharmony_ci /* handled by kernel: dell-laptop */ 7262306a36Sopenharmony_ci {0x0000, CLASS_INFO, SELECT_RFKILL}, 7362306a36Sopenharmony_ci {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, 7462306a36Sopenharmony_ci}; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistruct token_range { 7762306a36Sopenharmony_ci u32 need_capability; 7862306a36Sopenharmony_ci u16 min; 7962306a36Sopenharmony_ci u16 max; 8062306a36Sopenharmony_ci}; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci/* tokens that are whitelisted for given capabilities */ 8362306a36Sopenharmony_cistatic struct token_range token_whitelist[] = { 8462306a36Sopenharmony_ci /* used by userspace: fwupdate */ 8562306a36Sopenharmony_ci {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN, CAPSULE_DIS_TOKEN}, 8662306a36Sopenharmony_ci /* can indicate to userspace that WMI is needed */ 8762306a36Sopenharmony_ci {0x0000, WSMT_EN_TOKEN, WSMT_DIS_TOKEN} 8862306a36Sopenharmony_ci}; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci/* tokens that are explicitly blacklisted */ 9162306a36Sopenharmony_cistatic struct token_range token_blacklist[] = { 9262306a36Sopenharmony_ci {0x0000, 0x0058, 0x0059}, /* ME use */ 9362306a36Sopenharmony_ci {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */ 9462306a36Sopenharmony_ci {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */ 9562306a36Sopenharmony_ci {0x0000, 0x0175, 0x0176}, /* write once */ 9662306a36Sopenharmony_ci {0x0000, 0x0195, 0x0197}, /* diagnostics */ 9762306a36Sopenharmony_ci {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */ 9862306a36Sopenharmony_ci {0x0000, 0x027D, 0x0284}, /* diagnostics */ 9962306a36Sopenharmony_ci {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */ 10062306a36Sopenharmony_ci {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */ 10162306a36Sopenharmony_ci {0x0000, 0x0300, 0x0302}, /* manufacturing use */ 10262306a36Sopenharmony_ci {0x0000, 0x0325, 0x0326}, /* manufacturing use */ 10362306a36Sopenharmony_ci {0x0000, 0x0332, 0x0335}, /* fan control */ 10462306a36Sopenharmony_ci {0x0000, 0x0350, 0x0350}, /* manufacturing use */ 10562306a36Sopenharmony_ci {0x0000, 0x0363, 0x0363}, /* manufacturing use */ 10662306a36Sopenharmony_ci {0x0000, 0x0368, 0x0368}, /* manufacturing use */ 10762306a36Sopenharmony_ci {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */ 10862306a36Sopenharmony_ci {0x0000, 0x049E, 0x049F}, /* manufacturing use */ 10962306a36Sopenharmony_ci {0x0000, 0x04A0, 0x04A3}, /* disagnostics */ 11062306a36Sopenharmony_ci {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */ 11162306a36Sopenharmony_ci {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */ 11262306a36Sopenharmony_ci {0x0000, 0x9000, 0x9001}, /* internal BIOS use */ 11362306a36Sopenharmony_ci {0x0000, 0xA000, 0xBFFF}, /* write only */ 11462306a36Sopenharmony_ci {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */ 11562306a36Sopenharmony_ci /* handled by kernel: dell-laptop */ 11662306a36Sopenharmony_ci {0x0000, BRIGHTNESS_TOKEN, BRIGHTNESS_TOKEN}, 11762306a36Sopenharmony_ci {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN}, 11862306a36Sopenharmony_ci {0x0000, KBD_LED_AC_TOKEN, KBD_LED_AC_TOKEN}, 11962306a36Sopenharmony_ci {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN}, 12062306a36Sopenharmony_ci {0x0000, KBD_LED_AUTO_100_TOKEN, KBD_LED_AUTO_100_TOKEN}, 12162306a36Sopenharmony_ci {0x0000, GLOBAL_MIC_MUTE_ENABLE, GLOBAL_MIC_MUTE_DISABLE}, 12262306a36Sopenharmony_ci}; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic LIST_HEAD(smbios_device_list); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ciint dell_smbios_error(int value) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci switch (value) { 12962306a36Sopenharmony_ci case 0: /* Completed successfully */ 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci case -1: /* Completed with error */ 13262306a36Sopenharmony_ci return -EIO; 13362306a36Sopenharmony_ci case -2: /* Function not supported */ 13462306a36Sopenharmony_ci return -ENXIO; 13562306a36Sopenharmony_ci default: /* Unknown error */ 13662306a36Sopenharmony_ci return -EINVAL; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_error); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ciint dell_smbios_register_device(struct device *d, void *call_fn) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci struct smbios_device *priv; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL); 14662306a36Sopenharmony_ci if (!priv) 14762306a36Sopenharmony_ci return -ENOMEM; 14862306a36Sopenharmony_ci get_device(d); 14962306a36Sopenharmony_ci priv->device = d; 15062306a36Sopenharmony_ci priv->call_fn = call_fn; 15162306a36Sopenharmony_ci mutex_lock(&smbios_mutex); 15262306a36Sopenharmony_ci list_add_tail(&priv->list, &smbios_device_list); 15362306a36Sopenharmony_ci mutex_unlock(&smbios_mutex); 15462306a36Sopenharmony_ci dev_dbg(d, "Added device: %s\n", d->driver->name); 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_register_device); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_civoid dell_smbios_unregister_device(struct device *d) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct smbios_device *priv; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci mutex_lock(&smbios_mutex); 16462306a36Sopenharmony_ci list_for_each_entry(priv, &smbios_device_list, list) { 16562306a36Sopenharmony_ci if (priv->device == d) { 16662306a36Sopenharmony_ci list_del(&priv->list); 16762306a36Sopenharmony_ci put_device(d); 16862306a36Sopenharmony_ci break; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci mutex_unlock(&smbios_mutex); 17262306a36Sopenharmony_ci dev_dbg(d, "Remove device: %s\n", d->driver->name); 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_unregister_device); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ciint dell_smbios_call_filter(struct device *d, 17762306a36Sopenharmony_ci struct calling_interface_buffer *buffer) 17862306a36Sopenharmony_ci{ 17962306a36Sopenharmony_ci u16 t = 0; 18062306a36Sopenharmony_ci int i; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* can't make calls over 30 */ 18362306a36Sopenharmony_ci if (buffer->cmd_class > 30) { 18462306a36Sopenharmony_ci dev_dbg(d, "class too big: %u\n", buffer->cmd_class); 18562306a36Sopenharmony_ci return -EINVAL; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* supported calls on the particular system */ 18962306a36Sopenharmony_ci if (!(da_supported_commands & (1 << buffer->cmd_class))) { 19062306a36Sopenharmony_ci dev_dbg(d, "invalid command, supported commands: 0x%8x\n", 19162306a36Sopenharmony_ci da_supported_commands); 19262306a36Sopenharmony_ci return -EINVAL; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci /* match against call blacklist */ 19662306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) { 19762306a36Sopenharmony_ci if (buffer->cmd_class != call_blacklist[i].cmd_class) 19862306a36Sopenharmony_ci continue; 19962306a36Sopenharmony_ci if (buffer->cmd_select != call_blacklist[i].cmd_select && 20062306a36Sopenharmony_ci call_blacklist[i].cmd_select != -1) 20162306a36Sopenharmony_ci continue; 20262306a36Sopenharmony_ci dev_dbg(d, "blacklisted command: %u/%u\n", 20362306a36Sopenharmony_ci buffer->cmd_class, buffer->cmd_select); 20462306a36Sopenharmony_ci return -EINVAL; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci /* if a token call, find token ID */ 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if ((buffer->cmd_class == CLASS_TOKEN_READ || 21062306a36Sopenharmony_ci buffer->cmd_class == CLASS_TOKEN_WRITE) && 21162306a36Sopenharmony_ci buffer->cmd_select < 3) { 21262306a36Sopenharmony_ci /* tokens enabled ? */ 21362306a36Sopenharmony_ci if (!da_tokens) { 21462306a36Sopenharmony_ci dev_dbg(d, "no token support on this system\n"); 21562306a36Sopenharmony_ci return -EINVAL; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci /* find the matching token ID */ 21962306a36Sopenharmony_ci for (i = 0; i < da_num_tokens; i++) { 22062306a36Sopenharmony_ci if (da_tokens[i].location != buffer->input[0]) 22162306a36Sopenharmony_ci continue; 22262306a36Sopenharmony_ci t = da_tokens[i].tokenID; 22362306a36Sopenharmony_ci break; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci /* token call; but token didn't exist */ 22762306a36Sopenharmony_ci if (!t) { 22862306a36Sopenharmony_ci dev_dbg(d, "token at location %04x doesn't exist\n", 22962306a36Sopenharmony_ci buffer->input[0]); 23062306a36Sopenharmony_ci return -EINVAL; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci /* match against token blacklist */ 23462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) { 23562306a36Sopenharmony_ci if (!token_blacklist[i].min || !token_blacklist[i].max) 23662306a36Sopenharmony_ci continue; 23762306a36Sopenharmony_ci if (t >= token_blacklist[i].min && 23862306a36Sopenharmony_ci t <= token_blacklist[i].max) 23962306a36Sopenharmony_ci return -EINVAL; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci /* match against token whitelist */ 24362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) { 24462306a36Sopenharmony_ci if (!token_whitelist[i].min || !token_whitelist[i].max) 24562306a36Sopenharmony_ci continue; 24662306a36Sopenharmony_ci if (t < token_whitelist[i].min || 24762306a36Sopenharmony_ci t > token_whitelist[i].max) 24862306a36Sopenharmony_ci continue; 24962306a36Sopenharmony_ci if (!token_whitelist[i].need_capability || 25062306a36Sopenharmony_ci capable(token_whitelist[i].need_capability)) { 25162306a36Sopenharmony_ci dev_dbg(d, "whitelisted token: %x\n", t); 25262306a36Sopenharmony_ci return 0; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci /* match against call whitelist */ 25862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) { 25962306a36Sopenharmony_ci if (buffer->cmd_class != call_whitelist[i].cmd_class) 26062306a36Sopenharmony_ci continue; 26162306a36Sopenharmony_ci if (buffer->cmd_select != call_whitelist[i].cmd_select) 26262306a36Sopenharmony_ci continue; 26362306a36Sopenharmony_ci if (!call_whitelist[i].need_capability || 26462306a36Sopenharmony_ci capable(call_whitelist[i].need_capability)) { 26562306a36Sopenharmony_ci dev_dbg(d, "whitelisted capable command: %u/%u\n", 26662306a36Sopenharmony_ci buffer->cmd_class, buffer->cmd_select); 26762306a36Sopenharmony_ci return 0; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci dev_dbg(d, "missing capability %d for %u/%u\n", 27062306a36Sopenharmony_ci call_whitelist[i].need_capability, 27162306a36Sopenharmony_ci buffer->cmd_class, buffer->cmd_select); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci /* not in a whitelist, only allow processes with capabilities */ 27662306a36Sopenharmony_ci if (capable(CAP_SYS_RAWIO)) { 27762306a36Sopenharmony_ci dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n", 27862306a36Sopenharmony_ci buffer->cmd_class, buffer->cmd_select); 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return -EACCES; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_call_filter); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ciint dell_smbios_call(struct calling_interface_buffer *buffer) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci int (*call_fn)(struct calling_interface_buffer *) = NULL; 28962306a36Sopenharmony_ci struct device *selected_dev = NULL; 29062306a36Sopenharmony_ci struct smbios_device *priv; 29162306a36Sopenharmony_ci int ret; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci mutex_lock(&smbios_mutex); 29462306a36Sopenharmony_ci list_for_each_entry(priv, &smbios_device_list, list) { 29562306a36Sopenharmony_ci if (!selected_dev || priv->device->id >= selected_dev->id) { 29662306a36Sopenharmony_ci dev_dbg(priv->device, "Trying device ID: %d\n", 29762306a36Sopenharmony_ci priv->device->id); 29862306a36Sopenharmony_ci call_fn = priv->call_fn; 29962306a36Sopenharmony_ci selected_dev = priv->device; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (!selected_dev) { 30462306a36Sopenharmony_ci ret = -ENODEV; 30562306a36Sopenharmony_ci pr_err("No dell-smbios drivers are loaded\n"); 30662306a36Sopenharmony_ci goto out_smbios_call; 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci ret = call_fn(buffer); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ciout_smbios_call: 31262306a36Sopenharmony_ci mutex_unlock(&smbios_mutex); 31362306a36Sopenharmony_ci return ret; 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_call); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_cistruct calling_interface_token *dell_smbios_find_token(int tokenid) 31862306a36Sopenharmony_ci{ 31962306a36Sopenharmony_ci int i; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci if (!da_tokens) 32262306a36Sopenharmony_ci return NULL; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci for (i = 0; i < da_num_tokens; i++) { 32562306a36Sopenharmony_ci if (da_tokens[i].tokenID == tokenid) 32662306a36Sopenharmony_ci return &da_tokens[i]; 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci return NULL; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_find_token); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ciint dell_laptop_register_notifier(struct notifier_block *nb) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci return blocking_notifier_chain_register(&dell_laptop_chain_head, nb); 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_laptop_register_notifier); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ciint dell_laptop_unregister_notifier(struct notifier_block *nb) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb); 34462306a36Sopenharmony_ci} 34562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_civoid dell_laptop_call_notifier(unsigned long action, void *data) 34862306a36Sopenharmony_ci{ 34962306a36Sopenharmony_ci blocking_notifier_call_chain(&dell_laptop_chain_head, action, data); 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_laptop_call_notifier); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic void __init parse_da_table(const struct dmi_header *dm) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci /* Final token is a terminator, so we don't want to copy it */ 35662306a36Sopenharmony_ci int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; 35762306a36Sopenharmony_ci struct calling_interface_token *new_da_tokens; 35862306a36Sopenharmony_ci struct calling_interface_structure *table = 35962306a36Sopenharmony_ci container_of(dm, struct calling_interface_structure, header); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci /* 36262306a36Sopenharmony_ci * 4 bytes of table header, plus 7 bytes of Dell header 36362306a36Sopenharmony_ci * plus at least 6 bytes of entry 36462306a36Sopenharmony_ci */ 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (dm->length < 17) 36762306a36Sopenharmony_ci return; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci da_supported_commands = table->supportedCmds; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * 37262306a36Sopenharmony_ci sizeof(struct calling_interface_token), 37362306a36Sopenharmony_ci GFP_KERNEL); 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci if (!new_da_tokens) 37662306a36Sopenharmony_ci return; 37762306a36Sopenharmony_ci da_tokens = new_da_tokens; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci memcpy(da_tokens+da_num_tokens, table->tokens, 38062306a36Sopenharmony_ci sizeof(struct calling_interface_token) * tokens); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci da_num_tokens += tokens; 38362306a36Sopenharmony_ci} 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic void zero_duplicates(struct device *dev) 38662306a36Sopenharmony_ci{ 38762306a36Sopenharmony_ci int i, j; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci for (i = 0; i < da_num_tokens; i++) { 39062306a36Sopenharmony_ci if (da_tokens[i].tokenID == 0) 39162306a36Sopenharmony_ci continue; 39262306a36Sopenharmony_ci for (j = i+1; j < da_num_tokens; j++) { 39362306a36Sopenharmony_ci if (da_tokens[j].tokenID == 0) 39462306a36Sopenharmony_ci continue; 39562306a36Sopenharmony_ci if (da_tokens[i].tokenID == da_tokens[j].tokenID) { 39662306a36Sopenharmony_ci dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", 39762306a36Sopenharmony_ci da_tokens[j].tokenID, 39862306a36Sopenharmony_ci da_tokens[j].location, 39962306a36Sopenharmony_ci da_tokens[j].value); 40062306a36Sopenharmony_ci da_tokens[j].tokenID = 0; 40162306a36Sopenharmony_ci } 40262306a36Sopenharmony_ci } 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci} 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cistatic void __init find_tokens(const struct dmi_header *dm, void *dummy) 40762306a36Sopenharmony_ci{ 40862306a36Sopenharmony_ci switch (dm->type) { 40962306a36Sopenharmony_ci case 0xd4: /* Indexed IO */ 41062306a36Sopenharmony_ci case 0xd5: /* Protected Area Type 1 */ 41162306a36Sopenharmony_ci case 0xd6: /* Protected Area Type 2 */ 41262306a36Sopenharmony_ci break; 41362306a36Sopenharmony_ci case 0xda: /* Calling interface */ 41462306a36Sopenharmony_ci parse_da_table(dm); 41562306a36Sopenharmony_ci break; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci} 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic int match_attribute(struct device *dev, 42062306a36Sopenharmony_ci struct device_attribute *attr) 42162306a36Sopenharmony_ci{ 42262306a36Sopenharmony_ci int i; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci for (i = 0; i < da_num_tokens * 2; i++) { 42562306a36Sopenharmony_ci if (!token_attrs[i]) 42662306a36Sopenharmony_ci continue; 42762306a36Sopenharmony_ci if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) 42862306a36Sopenharmony_ci return i/2; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); 43162306a36Sopenharmony_ci return -EINVAL; 43262306a36Sopenharmony_ci} 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_cistatic ssize_t location_show(struct device *dev, 43562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 43662306a36Sopenharmony_ci{ 43762306a36Sopenharmony_ci int i; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 44062306a36Sopenharmony_ci return -EPERM; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci i = match_attribute(dev, attr); 44362306a36Sopenharmony_ci if (i > 0) 44462306a36Sopenharmony_ci return sysfs_emit(buf, "%08x", da_tokens[i].location); 44562306a36Sopenharmony_ci return 0; 44662306a36Sopenharmony_ci} 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_cistatic ssize_t value_show(struct device *dev, 44962306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci int i; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 45462306a36Sopenharmony_ci return -EPERM; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci i = match_attribute(dev, attr); 45762306a36Sopenharmony_ci if (i > 0) 45862306a36Sopenharmony_ci return sysfs_emit(buf, "%08x", da_tokens[i].value); 45962306a36Sopenharmony_ci return 0; 46062306a36Sopenharmony_ci} 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_cistatic struct attribute_group smbios_attribute_group = { 46362306a36Sopenharmony_ci .name = "tokens" 46462306a36Sopenharmony_ci}; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cistatic struct platform_driver platform_driver = { 46762306a36Sopenharmony_ci .driver = { 46862306a36Sopenharmony_ci .name = "dell-smbios", 46962306a36Sopenharmony_ci }, 47062306a36Sopenharmony_ci}; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_cistatic int build_tokens_sysfs(struct platform_device *dev) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci char *location_name; 47562306a36Sopenharmony_ci char *value_name; 47662306a36Sopenharmony_ci size_t size; 47762306a36Sopenharmony_ci int ret; 47862306a36Sopenharmony_ci int i, j; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci /* (number of tokens + 1 for null terminated */ 48162306a36Sopenharmony_ci size = sizeof(struct device_attribute) * (da_num_tokens + 1); 48262306a36Sopenharmony_ci token_location_attrs = kzalloc(size, GFP_KERNEL); 48362306a36Sopenharmony_ci if (!token_location_attrs) 48462306a36Sopenharmony_ci return -ENOMEM; 48562306a36Sopenharmony_ci token_value_attrs = kzalloc(size, GFP_KERNEL); 48662306a36Sopenharmony_ci if (!token_value_attrs) 48762306a36Sopenharmony_ci goto out_allocate_value; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci /* need to store both location and value + terminator*/ 49062306a36Sopenharmony_ci size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); 49162306a36Sopenharmony_ci token_attrs = kzalloc(size, GFP_KERNEL); 49262306a36Sopenharmony_ci if (!token_attrs) 49362306a36Sopenharmony_ci goto out_allocate_attrs; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci for (i = 0, j = 0; i < da_num_tokens; i++) { 49662306a36Sopenharmony_ci /* skip empty */ 49762306a36Sopenharmony_ci if (da_tokens[i].tokenID == 0) 49862306a36Sopenharmony_ci continue; 49962306a36Sopenharmony_ci /* add location */ 50062306a36Sopenharmony_ci location_name = kasprintf(GFP_KERNEL, "%04x_location", 50162306a36Sopenharmony_ci da_tokens[i].tokenID); 50262306a36Sopenharmony_ci if (location_name == NULL) 50362306a36Sopenharmony_ci goto out_unwind_strings; 50462306a36Sopenharmony_ci sysfs_attr_init(&token_location_attrs[i].attr); 50562306a36Sopenharmony_ci token_location_attrs[i].attr.name = location_name; 50662306a36Sopenharmony_ci token_location_attrs[i].attr.mode = 0444; 50762306a36Sopenharmony_ci token_location_attrs[i].show = location_show; 50862306a36Sopenharmony_ci token_attrs[j++] = &token_location_attrs[i].attr; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci /* add value */ 51162306a36Sopenharmony_ci value_name = kasprintf(GFP_KERNEL, "%04x_value", 51262306a36Sopenharmony_ci da_tokens[i].tokenID); 51362306a36Sopenharmony_ci if (value_name == NULL) 51462306a36Sopenharmony_ci goto loop_fail_create_value; 51562306a36Sopenharmony_ci sysfs_attr_init(&token_value_attrs[i].attr); 51662306a36Sopenharmony_ci token_value_attrs[i].attr.name = value_name; 51762306a36Sopenharmony_ci token_value_attrs[i].attr.mode = 0444; 51862306a36Sopenharmony_ci token_value_attrs[i].show = value_show; 51962306a36Sopenharmony_ci token_attrs[j++] = &token_value_attrs[i].attr; 52062306a36Sopenharmony_ci continue; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ciloop_fail_create_value: 52362306a36Sopenharmony_ci kfree(location_name); 52462306a36Sopenharmony_ci goto out_unwind_strings; 52562306a36Sopenharmony_ci } 52662306a36Sopenharmony_ci smbios_attribute_group.attrs = token_attrs; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); 52962306a36Sopenharmony_ci if (ret) 53062306a36Sopenharmony_ci goto out_unwind_strings; 53162306a36Sopenharmony_ci return 0; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ciout_unwind_strings: 53462306a36Sopenharmony_ci while (i--) { 53562306a36Sopenharmony_ci kfree(token_location_attrs[i].attr.name); 53662306a36Sopenharmony_ci kfree(token_value_attrs[i].attr.name); 53762306a36Sopenharmony_ci } 53862306a36Sopenharmony_ci kfree(token_attrs); 53962306a36Sopenharmony_ciout_allocate_attrs: 54062306a36Sopenharmony_ci kfree(token_value_attrs); 54162306a36Sopenharmony_ciout_allocate_value: 54262306a36Sopenharmony_ci kfree(token_location_attrs); 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci return -ENOMEM; 54562306a36Sopenharmony_ci} 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_cistatic void free_group(struct platform_device *pdev) 54862306a36Sopenharmony_ci{ 54962306a36Sopenharmony_ci int i; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci sysfs_remove_group(&pdev->dev.kobj, 55262306a36Sopenharmony_ci &smbios_attribute_group); 55362306a36Sopenharmony_ci for (i = 0; i < da_num_tokens; i++) { 55462306a36Sopenharmony_ci kfree(token_location_attrs[i].attr.name); 55562306a36Sopenharmony_ci kfree(token_value_attrs[i].attr.name); 55662306a36Sopenharmony_ci } 55762306a36Sopenharmony_ci kfree(token_attrs); 55862306a36Sopenharmony_ci kfree(token_value_attrs); 55962306a36Sopenharmony_ci kfree(token_location_attrs); 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_cistatic int __init dell_smbios_init(void) 56362306a36Sopenharmony_ci{ 56462306a36Sopenharmony_ci int ret, wmi, smm; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && 56762306a36Sopenharmony_ci !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { 56862306a36Sopenharmony_ci pr_err("Unable to run on non-Dell system\n"); 56962306a36Sopenharmony_ci return -ENODEV; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci dmi_walk(find_tokens, NULL); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci ret = platform_driver_register(&platform_driver); 57562306a36Sopenharmony_ci if (ret) 57662306a36Sopenharmony_ci goto fail_platform_driver; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci platform_device = platform_device_alloc("dell-smbios", 0); 57962306a36Sopenharmony_ci if (!platform_device) { 58062306a36Sopenharmony_ci ret = -ENOMEM; 58162306a36Sopenharmony_ci goto fail_platform_device_alloc; 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci ret = platform_device_add(platform_device); 58462306a36Sopenharmony_ci if (ret) 58562306a36Sopenharmony_ci goto fail_platform_device_add; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* register backends */ 58862306a36Sopenharmony_ci wmi = init_dell_smbios_wmi(); 58962306a36Sopenharmony_ci if (wmi) 59062306a36Sopenharmony_ci pr_debug("Failed to initialize WMI backend: %d\n", wmi); 59162306a36Sopenharmony_ci smm = init_dell_smbios_smm(); 59262306a36Sopenharmony_ci if (smm) 59362306a36Sopenharmony_ci pr_debug("Failed to initialize SMM backend: %d\n", smm); 59462306a36Sopenharmony_ci if (wmi && smm) { 59562306a36Sopenharmony_ci pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n", 59662306a36Sopenharmony_ci wmi, smm); 59762306a36Sopenharmony_ci ret = -ENODEV; 59862306a36Sopenharmony_ci goto fail_create_group; 59962306a36Sopenharmony_ci } 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci if (da_tokens) { 60262306a36Sopenharmony_ci /* duplicate tokens will cause problems building sysfs files */ 60362306a36Sopenharmony_ci zero_duplicates(&platform_device->dev); 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci ret = build_tokens_sysfs(platform_device); 60662306a36Sopenharmony_ci if (ret) 60762306a36Sopenharmony_ci goto fail_sysfs; 60862306a36Sopenharmony_ci } 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci return 0; 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_cifail_sysfs: 61362306a36Sopenharmony_ci free_group(platform_device); 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_cifail_create_group: 61662306a36Sopenharmony_ci platform_device_del(platform_device); 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_cifail_platform_device_add: 61962306a36Sopenharmony_ci platform_device_put(platform_device); 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_cifail_platform_device_alloc: 62262306a36Sopenharmony_ci platform_driver_unregister(&platform_driver); 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_cifail_platform_driver: 62562306a36Sopenharmony_ci kfree(da_tokens); 62662306a36Sopenharmony_ci return ret; 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_cistatic void __exit dell_smbios_exit(void) 63062306a36Sopenharmony_ci{ 63162306a36Sopenharmony_ci exit_dell_smbios_wmi(); 63262306a36Sopenharmony_ci exit_dell_smbios_smm(); 63362306a36Sopenharmony_ci mutex_lock(&smbios_mutex); 63462306a36Sopenharmony_ci if (platform_device) { 63562306a36Sopenharmony_ci if (da_tokens) 63662306a36Sopenharmony_ci free_group(platform_device); 63762306a36Sopenharmony_ci platform_device_unregister(platform_device); 63862306a36Sopenharmony_ci platform_driver_unregister(&platform_driver); 63962306a36Sopenharmony_ci } 64062306a36Sopenharmony_ci kfree(da_tokens); 64162306a36Sopenharmony_ci mutex_unlock(&smbios_mutex); 64262306a36Sopenharmony_ci} 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cimodule_init(dell_smbios_init); 64562306a36Sopenharmony_cimodule_exit(dell_smbios_exit); 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ciMODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); 64862306a36Sopenharmony_ciMODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>"); 64962306a36Sopenharmony_ciMODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); 65062306a36Sopenharmony_ciMODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); 65162306a36Sopenharmony_ciMODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS"); 65262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 653