dell-wmi-descriptor.c (4779B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Dell WMI descriptor driver 4 * 5 * Copyright (C) 2017 Dell Inc. All Rights Reserved. 6 */ 7 8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9 10#include <linux/acpi.h> 11#include <linux/list.h> 12#include <linux/module.h> 13#include <linux/wmi.h> 14#include "dell-wmi-descriptor.h" 15 16#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" 17 18struct descriptor_priv { 19 struct list_head list; 20 u32 interface_version; 21 u32 size; 22 u32 hotfix; 23}; 24static int descriptor_valid = -EPROBE_DEFER; 25static LIST_HEAD(wmi_list); 26static DEFINE_MUTEX(list_mutex); 27 28int dell_wmi_get_descriptor_valid(void) 29{ 30 if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) 31 return -ENODEV; 32 33 return descriptor_valid; 34} 35EXPORT_SYMBOL_GPL(dell_wmi_get_descriptor_valid); 36 37bool dell_wmi_get_interface_version(u32 *version) 38{ 39 struct descriptor_priv *priv; 40 bool ret = false; 41 42 mutex_lock(&list_mutex); 43 priv = list_first_entry_or_null(&wmi_list, 44 struct descriptor_priv, 45 list); 46 if (priv) { 47 *version = priv->interface_version; 48 ret = true; 49 } 50 mutex_unlock(&list_mutex); 51 return ret; 52} 53EXPORT_SYMBOL_GPL(dell_wmi_get_interface_version); 54 55bool dell_wmi_get_size(u32 *size) 56{ 57 struct descriptor_priv *priv; 58 bool ret = false; 59 60 mutex_lock(&list_mutex); 61 priv = list_first_entry_or_null(&wmi_list, 62 struct descriptor_priv, 63 list); 64 if (priv) { 65 *size = priv->size; 66 ret = true; 67 } 68 mutex_unlock(&list_mutex); 69 return ret; 70} 71EXPORT_SYMBOL_GPL(dell_wmi_get_size); 72 73bool dell_wmi_get_hotfix(u32 *hotfix) 74{ 75 struct descriptor_priv *priv; 76 bool ret = false; 77 78 mutex_lock(&list_mutex); 79 priv = list_first_entry_or_null(&wmi_list, 80 struct descriptor_priv, 81 list); 82 if (priv) { 83 *hotfix = priv->hotfix; 84 ret = true; 85 } 86 mutex_unlock(&list_mutex); 87 return ret; 88} 89EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); 90 91/* 92 * Descriptor buffer is 128 byte long and contains: 93 * 94 * Name Offset Length Value 95 * Vendor Signature 0 4 "DELL" 96 * Object Signature 4 4 " WMI" 97 * WMI Interface Version 8 4 <version> 98 * WMI buffer length 12 4 <length> 99 * WMI hotfix number 16 4 <hotfix> 100 */ 101static int dell_wmi_descriptor_probe(struct wmi_device *wdev, 102 const void *context) 103{ 104 union acpi_object *obj = NULL; 105 struct descriptor_priv *priv; 106 u32 *buffer; 107 int ret; 108 109 obj = wmidev_block_query(wdev, 0); 110 if (!obj) { 111 dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); 112 ret = -EIO; 113 goto out; 114 } 115 116 if (obj->type != ACPI_TYPE_BUFFER) { 117 dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); 118 ret = -EINVAL; 119 descriptor_valid = ret; 120 goto out; 121 } 122 123 /* Although it's not technically a failure, this would lead to 124 * unexpected behavior 125 */ 126 if (obj->buffer.length != 128) { 127 dev_err(&wdev->dev, 128 "Dell descriptor buffer has unexpected length (%d)\n", 129 obj->buffer.length); 130 ret = -EINVAL; 131 descriptor_valid = ret; 132 goto out; 133 } 134 135 buffer = (u32 *)obj->buffer.pointer; 136 137 if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { 138 dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", 139 buffer); 140 ret = -EINVAL; 141 descriptor_valid = ret; 142 goto out; 143 } 144 descriptor_valid = 0; 145 146 if (buffer[2] != 0 && buffer[2] != 1) 147 dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", 148 (unsigned long) buffer[2]); 149 150 priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv), 151 GFP_KERNEL); 152 153 if (!priv) { 154 ret = -ENOMEM; 155 goto out; 156 } 157 158 priv->interface_version = buffer[2]; 159 priv->size = buffer[3]; 160 priv->hotfix = buffer[4]; 161 ret = 0; 162 dev_set_drvdata(&wdev->dev, priv); 163 mutex_lock(&list_mutex); 164 list_add_tail(&priv->list, &wmi_list); 165 mutex_unlock(&list_mutex); 166 167 dev_dbg(&wdev->dev, "Detected Dell WMI interface version %lu, buffer size %lu, hotfix %lu\n", 168 (unsigned long) priv->interface_version, 169 (unsigned long) priv->size, 170 (unsigned long) priv->hotfix); 171 172out: 173 kfree(obj); 174 return ret; 175} 176 177static void dell_wmi_descriptor_remove(struct wmi_device *wdev) 178{ 179 struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev); 180 181 mutex_lock(&list_mutex); 182 list_del(&priv->list); 183 mutex_unlock(&list_mutex); 184} 185 186static const struct wmi_device_id dell_wmi_descriptor_id_table[] = { 187 { .guid_string = DELL_WMI_DESCRIPTOR_GUID }, 188 { }, 189}; 190 191static struct wmi_driver dell_wmi_descriptor_driver = { 192 .driver = { 193 .name = "dell-wmi-descriptor", 194 }, 195 .probe = dell_wmi_descriptor_probe, 196 .remove = dell_wmi_descriptor_remove, 197 .id_table = dell_wmi_descriptor_id_table, 198}; 199 200module_wmi_driver(dell_wmi_descriptor_driver); 201 202MODULE_DEVICE_TABLE(wmi, dell_wmi_descriptor_id_table); 203MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); 204MODULE_DESCRIPTION("Dell WMI descriptor driver"); 205MODULE_LICENSE("GPL");