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