linux/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship
   3 *
   4 * Copyright (c) 2014 Intel Corp
   5 */
   6
   7/*
   8 * Two functionalities included:
   9 * 1. Export _TRT, _ART, via misc device interface to the userspace.
  10 * 2. Provide parsing result to kernel drivers
  11 *
  12 */
  13#include <linux/init.h>
  14#include <linux/export.h>
  15#include <linux/module.h>
  16#include <linux/device.h>
  17#include <linux/platform_device.h>
  18#include <linux/io.h>
  19#include <linux/acpi.h>
  20#include <linux/uaccess.h>
  21#include <linux/miscdevice.h>
  22#include "acpi_thermal_rel.h"
  23
  24static acpi_handle acpi_thermal_rel_handle;
  25static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock);
  26static int acpi_thermal_rel_chrdev_count;       /* #times opened */
  27static int acpi_thermal_rel_chrdev_exclu;       /* already open exclusive? */
  28
  29static int acpi_thermal_rel_open(struct inode *inode, struct file *file)
  30{
  31        spin_lock(&acpi_thermal_rel_chrdev_lock);
  32        if (acpi_thermal_rel_chrdev_exclu ||
  33            (acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) {
  34                spin_unlock(&acpi_thermal_rel_chrdev_lock);
  35                return -EBUSY;
  36        }
  37
  38        if (file->f_flags & O_EXCL)
  39                acpi_thermal_rel_chrdev_exclu = 1;
  40        acpi_thermal_rel_chrdev_count++;
  41
  42        spin_unlock(&acpi_thermal_rel_chrdev_lock);
  43
  44        return nonseekable_open(inode, file);
  45}
  46
  47static int acpi_thermal_rel_release(struct inode *inode, struct file *file)
  48{
  49        spin_lock(&acpi_thermal_rel_chrdev_lock);
  50        acpi_thermal_rel_chrdev_count--;
  51        acpi_thermal_rel_chrdev_exclu = 0;
  52        spin_unlock(&acpi_thermal_rel_chrdev_lock);
  53
  54        return 0;
  55}
  56
  57/**
  58 * acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling
  59 *
  60 * @handle: ACPI handle of the device contains _TRT
  61 * @trt_count: the number of valid entries resulted from parsing _TRT
  62 * @trtp: pointer to pointer of array of _TRT entries in parsing result
  63 * @create_dev: whether to create platform devices for target and source
  64 *
  65 */
  66int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp,
  67                bool create_dev)
  68{
  69        acpi_status status;
  70        int result = 0;
  71        int i;
  72        int nr_bad_entries = 0;
  73        struct trt *trts;
  74        struct acpi_device *adev;
  75        union acpi_object *p;
  76        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
  77        struct acpi_buffer element = { 0, NULL };
  78        struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" };
  79
  80        if (!acpi_has_method(handle, "_TRT"))
  81                return -ENODEV;
  82
  83        status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer);
  84        if (ACPI_FAILURE(status))
  85                return -ENODEV;
  86
  87        p = buffer.pointer;
  88        if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
  89                pr_err("Invalid _TRT data\n");
  90                result = -EFAULT;
  91                goto end;
  92        }
  93
  94        *trt_count = p->package.count;
  95        trts = kcalloc(*trt_count, sizeof(struct trt), GFP_KERNEL);
  96        if (!trts) {
  97                result = -ENOMEM;
  98                goto end;
  99        }
 100
 101        for (i = 0; i < *trt_count; i++) {
 102                struct trt *trt = &trts[i - nr_bad_entries];
 103
 104                element.length = sizeof(struct trt);
 105                element.pointer = trt;
 106
 107                status = acpi_extract_package(&(p->package.elements[i]),
 108                                              &trt_format, &element);
 109                if (ACPI_FAILURE(status)) {
 110                        nr_bad_entries++;
 111                        pr_warn("_TRT package %d is invalid, ignored\n", i);
 112                        continue;
 113                }
 114                if (!create_dev)
 115                        continue;
 116
 117                result = acpi_bus_get_device(trt->source, &adev);
 118                if (result)
 119                        pr_warn("Failed to get source ACPI device\n");
 120
 121                result = acpi_bus_get_device(trt->target, &adev);
 122                if (result)
 123                        pr_warn("Failed to get target ACPI device\n");
 124        }
 125
 126        result = 0;
 127
 128        *trtp = trts;
 129        /* don't count bad entries */
 130        *trt_count -= nr_bad_entries;
 131end:
 132        kfree(buffer.pointer);
 133        return result;
 134}
 135EXPORT_SYMBOL(acpi_parse_trt);
 136
 137/**
 138 * acpi_parse_art - Parse Active Relationship Table _ART
 139 *
 140 * @handle: ACPI handle of the device contains _ART
 141 * @art_count: the number of valid entries resulted from parsing _ART
 142 * @artp: pointer to pointer of array of art entries in parsing result
 143 * @create_dev: whether to create platform devices for target and source
 144 *
 145 */
 146int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp,
 147                bool create_dev)
 148{
 149        acpi_status status;
 150        int result = 0;
 151        int i;
 152        int nr_bad_entries = 0;
 153        struct art *arts;
 154        struct acpi_device *adev;
 155        union acpi_object *p;
 156        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 157        struct acpi_buffer element = { 0, NULL };
 158        struct acpi_buffer art_format = {
 159                sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" };
 160
 161        if (!acpi_has_method(handle, "_ART"))
 162                return -ENODEV;
 163
 164        status = acpi_evaluate_object(handle, "_ART", NULL, &buffer);
 165        if (ACPI_FAILURE(status))
 166                return -ENODEV;
 167
 168        p = buffer.pointer;
 169        if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
 170                pr_err("Invalid _ART data\n");
 171                result = -EFAULT;
 172                goto end;
 173        }
 174
 175        /* ignore p->package.elements[0], as this is _ART Revision field */
 176        *art_count = p->package.count - 1;
 177        arts = kcalloc(*art_count, sizeof(struct art), GFP_KERNEL);
 178        if (!arts) {
 179                result = -ENOMEM;
 180                goto end;
 181        }
 182
 183        for (i = 0; i < *art_count; i++) {
 184                struct art *art = &arts[i - nr_bad_entries];
 185
 186                element.length = sizeof(struct art);
 187                element.pointer = art;
 188
 189                status = acpi_extract_package(&(p->package.elements[i + 1]),
 190                                              &art_format, &element);
 191                if (ACPI_FAILURE(status)) {
 192                        pr_warn("_ART package %d is invalid, ignored", i);
 193                        nr_bad_entries++;
 194                        continue;
 195                }
 196                if (!create_dev)
 197                        continue;
 198
 199                if (art->source) {
 200                        result = acpi_bus_get_device(art->source, &adev);
 201                        if (result)
 202                                pr_warn("Failed to get source ACPI device\n");
 203                }
 204                if (art->target) {
 205                        result = acpi_bus_get_device(art->target, &adev);
 206                        if (result)
 207                                pr_warn("Failed to get target ACPI device\n");
 208                }
 209        }
 210
 211        *artp = arts;
 212        /* don't count bad entries */
 213        *art_count -= nr_bad_entries;
 214end:
 215        kfree(buffer.pointer);
 216        return result;
 217}
 218EXPORT_SYMBOL(acpi_parse_art);
 219
 220
 221/* get device name from acpi handle */
 222static void get_single_name(acpi_handle handle, char *name)
 223{
 224        struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER};
 225
 226        if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)))
 227                pr_warn("Failed to get device name from acpi handle\n");
 228        else {
 229                memcpy(name, buffer.pointer, ACPI_NAMESEG_SIZE);
 230                kfree(buffer.pointer);
 231        }
 232}
 233
 234static int fill_art(char __user *ubuf)
 235{
 236        int i;
 237        int ret;
 238        int count;
 239        int art_len;
 240        struct art *arts = NULL;
 241        union art_object *art_user;
 242
 243        ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false);
 244        if (ret)
 245                goto free_art;
 246        art_len = count * sizeof(union art_object);
 247        art_user = kzalloc(art_len, GFP_KERNEL);
 248        if (!art_user) {
 249                ret = -ENOMEM;
 250                goto free_art;
 251        }
 252        /* now fill in user art data */
 253        for (i = 0; i < count; i++) {
 254                /* userspace art needs device name instead of acpi reference */
 255                get_single_name(arts[i].source, art_user[i].source_device);
 256                get_single_name(arts[i].target, art_user[i].target_device);
 257                /* copy the rest int data in addition to source and target */
 258                memcpy(&art_user[i].weight, &arts[i].weight,
 259                        sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));
 260        }
 261
 262        if (copy_to_user(ubuf, art_user, art_len))
 263                ret = -EFAULT;
 264        kfree(art_user);
 265free_art:
 266        kfree(arts);
 267        return ret;
 268}
 269
 270static int fill_trt(char __user *ubuf)
 271{
 272        int i;
 273        int ret;
 274        int count;
 275        int trt_len;
 276        struct trt *trts = NULL;
 277        union trt_object *trt_user;
 278
 279        ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false);
 280        if (ret)
 281                goto free_trt;
 282        trt_len = count * sizeof(union trt_object);
 283        trt_user = kzalloc(trt_len, GFP_KERNEL);
 284        if (!trt_user) {
 285                ret = -ENOMEM;
 286                goto free_trt;
 287        }
 288        /* now fill in user trt data */
 289        for (i = 0; i < count; i++) {
 290                /* userspace trt needs device name instead of acpi reference */
 291                get_single_name(trts[i].source, trt_user[i].source_device);
 292                get_single_name(trts[i].target, trt_user[i].target_device);
 293                trt_user[i].sample_period = trts[i].sample_period;
 294                trt_user[i].influence = trts[i].influence;
 295        }
 296
 297        if (copy_to_user(ubuf, trt_user, trt_len))
 298                ret = -EFAULT;
 299        kfree(trt_user);
 300free_trt:
 301        kfree(trts);
 302        return ret;
 303}
 304
 305static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd,
 306                                   unsigned long __arg)
 307{
 308        int ret = 0;
 309        unsigned long length = 0;
 310        int count = 0;
 311        char __user *arg = (void __user *)__arg;
 312        struct trt *trts = NULL;
 313        struct art *arts = NULL;
 314
 315        switch (cmd) {
 316        case ACPI_THERMAL_GET_TRT_COUNT:
 317                ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
 318                                &trts, false);
 319                kfree(trts);
 320                if (!ret)
 321                        return put_user(count, (unsigned long __user *)__arg);
 322                return ret;
 323        case ACPI_THERMAL_GET_TRT_LEN:
 324                ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
 325                                &trts, false);
 326                kfree(trts);
 327                length = count * sizeof(union trt_object);
 328                if (!ret)
 329                        return put_user(length, (unsigned long __user *)__arg);
 330                return ret;
 331        case ACPI_THERMAL_GET_TRT:
 332                return fill_trt(arg);
 333        case ACPI_THERMAL_GET_ART_COUNT:
 334                ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
 335                                &arts, false);
 336                kfree(arts);
 337                if (!ret)
 338                        return put_user(count, (unsigned long __user *)__arg);
 339                return ret;
 340        case ACPI_THERMAL_GET_ART_LEN:
 341                ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
 342                                &arts, false);
 343                kfree(arts);
 344                length = count * sizeof(union art_object);
 345                if (!ret)
 346                        return put_user(length, (unsigned long __user *)__arg);
 347                return ret;
 348
 349        case ACPI_THERMAL_GET_ART:
 350                return fill_art(arg);
 351
 352        default:
 353                return -ENOTTY;
 354        }
 355}
 356
 357static const struct file_operations acpi_thermal_rel_fops = {
 358        .owner          = THIS_MODULE,
 359        .open           = acpi_thermal_rel_open,
 360        .release        = acpi_thermal_rel_release,
 361        .unlocked_ioctl = acpi_thermal_rel_ioctl,
 362        .llseek         = no_llseek,
 363};
 364
 365static struct miscdevice acpi_thermal_rel_misc_device = {
 366        .minor  = MISC_DYNAMIC_MINOR,
 367        "acpi_thermal_rel",
 368        &acpi_thermal_rel_fops
 369};
 370
 371int acpi_thermal_rel_misc_device_add(acpi_handle handle)
 372{
 373        acpi_thermal_rel_handle = handle;
 374
 375        return misc_register(&acpi_thermal_rel_misc_device);
 376}
 377EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add);
 378
 379int acpi_thermal_rel_misc_device_remove(acpi_handle handle)
 380{
 381        misc_deregister(&acpi_thermal_rel_misc_device);
 382
 383        return 0;
 384}
 385EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove);
 386
 387MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
 388MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com");
 389MODULE_DESCRIPTION("Intel acpi thermal rel misc dev driver");
 390MODULE_LICENSE("GPL v2");
 391