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