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