434 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * ACPI Platform Firmware Runtime Telemetry driver4 *5 * Copyright (C) 2021 Intel Corporation6 * Author: Chen Yu <yu.c.chen@intel.com>7 *8 * This driver allows user space to fetch telemetry data from the9 * firmware with the help of the Platform Firmware Runtime Telemetry10 * interface.11 */12#include <linux/acpi.h>13#include <linux/device.h>14#include <linux/err.h>15#include <linux/errno.h>16#include <linux/file.h>17#include <linux/fs.h>18#include <linux/miscdevice.h>19#include <linux/module.h>20#include <linux/mm.h>21#include <linux/platform_device.h>22#include <linux/string.h>23#include <linux/uaccess.h>24#include <linux/uio.h>25#include <linux/uuid.h>26 27#include <uapi/linux/pfrut.h>28 29#define PFRT_LOG_EXEC_IDX 030#define PFRT_LOG_HISTORY_IDX 131 32#define PFRT_LOG_ERR 033#define PFRT_LOG_WARN 134#define PFRT_LOG_INFO 235#define PFRT_LOG_VERB 436 37#define PFRT_FUNC_SET_LEV 138#define PFRT_FUNC_GET_LEV 239#define PFRT_FUNC_GET_DATA 340 41#define PFRT_REVID_1 142#define PFRT_REVID_2 243#define PFRT_DEFAULT_REV_ID PFRT_REVID_144 45enum log_index {46 LOG_STATUS_IDX = 0,47 LOG_EXT_STATUS_IDX = 1,48 LOG_MAX_SZ_IDX = 2,49 LOG_CHUNK1_LO_IDX = 3,50 LOG_CHUNK1_HI_IDX = 4,51 LOG_CHUNK1_SZ_IDX = 5,52 LOG_CHUNK2_LO_IDX = 6,53 LOG_CHUNK2_HI_IDX = 7,54 LOG_CHUNK2_SZ_IDX = 8,55 LOG_ROLLOVER_CNT_IDX = 9,56 LOG_RESET_CNT_IDX = 10,57 LOG_NR_IDX58};59 60struct pfrt_log_device {61 int index;62 struct pfrt_log_info info;63 struct device *parent_dev;64 struct miscdevice miscdev;65};66 67/* pfrt_guid is the parameter for _DSM method */68static const guid_t pfrt_log_guid =69 GUID_INIT(0x75191659, 0x8178, 0x4D9D, 0xB8, 0x8F, 0xAC, 0x5E,70 0x5E, 0x93, 0xE8, 0xBF);71 72static DEFINE_IDA(pfrt_log_ida);73 74static inline struct pfrt_log_device *to_pfrt_log_dev(struct file *file)75{76 return container_of(file->private_data, struct pfrt_log_device, miscdev);77}78 79static int get_pfrt_log_data_info(struct pfrt_log_data_info *data_info,80 struct pfrt_log_device *pfrt_log_dev)81{82 acpi_handle handle = ACPI_HANDLE(pfrt_log_dev->parent_dev);83 union acpi_object *out_obj, in_obj, in_buf;84 int ret = -EBUSY;85 86 memset(data_info, 0, sizeof(*data_info));87 memset(&in_obj, 0, sizeof(in_obj));88 memset(&in_buf, 0, sizeof(in_buf));89 in_obj.type = ACPI_TYPE_PACKAGE;90 in_obj.package.count = 1;91 in_obj.package.elements = &in_buf;92 in_buf.type = ACPI_TYPE_INTEGER;93 in_buf.integer.value = pfrt_log_dev->info.log_type;94 95 out_obj = acpi_evaluate_dsm_typed(handle, &pfrt_log_guid,96 pfrt_log_dev->info.log_revid, PFRT_FUNC_GET_DATA,97 &in_obj, ACPI_TYPE_PACKAGE);98 if (!out_obj)99 return -EINVAL;100 101 if (out_obj->package.count < LOG_NR_IDX ||102 out_obj->package.elements[LOG_STATUS_IDX].type != ACPI_TYPE_INTEGER ||103 out_obj->package.elements[LOG_EXT_STATUS_IDX].type != ACPI_TYPE_INTEGER ||104 out_obj->package.elements[LOG_MAX_SZ_IDX].type != ACPI_TYPE_INTEGER ||105 out_obj->package.elements[LOG_CHUNK1_LO_IDX].type != ACPI_TYPE_INTEGER ||106 out_obj->package.elements[LOG_CHUNK1_HI_IDX].type != ACPI_TYPE_INTEGER ||107 out_obj->package.elements[LOG_CHUNK1_SZ_IDX].type != ACPI_TYPE_INTEGER ||108 out_obj->package.elements[LOG_CHUNK2_LO_IDX].type != ACPI_TYPE_INTEGER ||109 out_obj->package.elements[LOG_CHUNK2_HI_IDX].type != ACPI_TYPE_INTEGER ||110 out_obj->package.elements[LOG_CHUNK2_SZ_IDX].type != ACPI_TYPE_INTEGER ||111 out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].type != ACPI_TYPE_INTEGER ||112 out_obj->package.elements[LOG_RESET_CNT_IDX].type != ACPI_TYPE_INTEGER)113 goto free_acpi_buffer;114 115 data_info->status = out_obj->package.elements[LOG_STATUS_IDX].integer.value;116 data_info->ext_status =117 out_obj->package.elements[LOG_EXT_STATUS_IDX].integer.value;118 if (data_info->status != DSM_SUCCEED) {119 dev_dbg(pfrt_log_dev->parent_dev, "Error Status:%d\n", data_info->status);120 dev_dbg(pfrt_log_dev->parent_dev, "Error Extend Status:%d\n",121 data_info->ext_status);122 goto free_acpi_buffer;123 }124 125 data_info->max_data_size =126 out_obj->package.elements[LOG_MAX_SZ_IDX].integer.value;127 data_info->chunk1_addr_lo =128 out_obj->package.elements[LOG_CHUNK1_LO_IDX].integer.value;129 data_info->chunk1_addr_hi =130 out_obj->package.elements[LOG_CHUNK1_HI_IDX].integer.value;131 data_info->chunk1_size =132 out_obj->package.elements[LOG_CHUNK1_SZ_IDX].integer.value;133 data_info->chunk2_addr_lo =134 out_obj->package.elements[LOG_CHUNK2_LO_IDX].integer.value;135 data_info->chunk2_addr_hi =136 out_obj->package.elements[LOG_CHUNK2_HI_IDX].integer.value;137 data_info->chunk2_size =138 out_obj->package.elements[LOG_CHUNK2_SZ_IDX].integer.value;139 data_info->rollover_cnt =140 out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].integer.value;141 data_info->reset_cnt =142 out_obj->package.elements[LOG_RESET_CNT_IDX].integer.value;143 144 ret = 0;145 146free_acpi_buffer:147 ACPI_FREE(out_obj);148 149 return ret;150}151 152static int set_pfrt_log_level(int level, struct pfrt_log_device *pfrt_log_dev)153{154 acpi_handle handle = ACPI_HANDLE(pfrt_log_dev->parent_dev);155 union acpi_object *out_obj, *obj, in_obj, in_buf;156 enum pfru_dsm_status status, ext_status;157 int ret = 0;158 159 memset(&in_obj, 0, sizeof(in_obj));160 memset(&in_buf, 0, sizeof(in_buf));161 in_obj.type = ACPI_TYPE_PACKAGE;162 in_obj.package.count = 1;163 in_obj.package.elements = &in_buf;164 in_buf.type = ACPI_TYPE_INTEGER;165 in_buf.integer.value = level;166 167 out_obj = acpi_evaluate_dsm_typed(handle, &pfrt_log_guid,168 pfrt_log_dev->info.log_revid, PFRT_FUNC_SET_LEV,169 &in_obj, ACPI_TYPE_PACKAGE);170 if (!out_obj)171 return -EINVAL;172 173 obj = &out_obj->package.elements[0];174 status = obj->integer.value;175 if (status != DSM_SUCCEED) {176 obj = &out_obj->package.elements[1];177 ext_status = obj->integer.value;178 dev_dbg(pfrt_log_dev->parent_dev, "Error Status:%d\n", status);179 dev_dbg(pfrt_log_dev->parent_dev, "Error Extend Status:%d\n", ext_status);180 ret = -EBUSY;181 }182 183 ACPI_FREE(out_obj);184 185 return ret;186}187 188static int get_pfrt_log_level(struct pfrt_log_device *pfrt_log_dev)189{190 acpi_handle handle = ACPI_HANDLE(pfrt_log_dev->parent_dev);191 union acpi_object *out_obj, *obj;192 enum pfru_dsm_status status, ext_status;193 int ret = -EBUSY;194 195 out_obj = acpi_evaluate_dsm_typed(handle, &pfrt_log_guid,196 pfrt_log_dev->info.log_revid, PFRT_FUNC_GET_LEV,197 NULL, ACPI_TYPE_PACKAGE);198 if (!out_obj)199 return -EINVAL;200 201 obj = &out_obj->package.elements[0];202 if (obj->type != ACPI_TYPE_INTEGER)203 goto free_acpi_buffer;204 205 status = obj->integer.value;206 if (status != DSM_SUCCEED) {207 obj = &out_obj->package.elements[1];208 ext_status = obj->integer.value;209 dev_dbg(pfrt_log_dev->parent_dev, "Error Status:%d\n", status);210 dev_dbg(pfrt_log_dev->parent_dev, "Error Extend Status:%d\n", ext_status);211 goto free_acpi_buffer;212 }213 214 obj = &out_obj->package.elements[2];215 if (obj->type != ACPI_TYPE_INTEGER)216 goto free_acpi_buffer;217 218 ret = obj->integer.value;219 220free_acpi_buffer:221 ACPI_FREE(out_obj);222 223 return ret;224}225 226static int valid_log_level(u32 level)227{228 return level == PFRT_LOG_ERR || level == PFRT_LOG_WARN ||229 level == PFRT_LOG_INFO || level == PFRT_LOG_VERB;230}231 232static int valid_log_type(u32 type)233{234 return type == PFRT_LOG_EXEC_IDX || type == PFRT_LOG_HISTORY_IDX;235}236 237static inline int valid_log_revid(u32 id)238{239 return id == PFRT_REVID_1 || id == PFRT_REVID_2;240}241 242static long pfrt_log_ioctl(struct file *file, unsigned int cmd, unsigned long arg)243{244 struct pfrt_log_device *pfrt_log_dev = to_pfrt_log_dev(file);245 struct pfrt_log_data_info data_info;246 struct pfrt_log_info info;247 void __user *p;248 int ret = 0;249 250 p = (void __user *)arg;251 252 switch (cmd) {253 case PFRT_LOG_IOC_SET_INFO:254 if (copy_from_user(&info, p, sizeof(info)))255 return -EFAULT;256 257 if (valid_log_revid(info.log_revid))258 pfrt_log_dev->info.log_revid = info.log_revid;259 260 if (valid_log_level(info.log_level)) {261 ret = set_pfrt_log_level(info.log_level, pfrt_log_dev);262 if (ret < 0)263 return ret;264 265 pfrt_log_dev->info.log_level = info.log_level;266 }267 268 if (valid_log_type(info.log_type))269 pfrt_log_dev->info.log_type = info.log_type;270 271 return 0;272 273 case PFRT_LOG_IOC_GET_INFO:274 info.log_level = get_pfrt_log_level(pfrt_log_dev);275 if (ret < 0)276 return ret;277 278 info.log_type = pfrt_log_dev->info.log_type;279 info.log_revid = pfrt_log_dev->info.log_revid;280 if (copy_to_user(p, &info, sizeof(info)))281 return -EFAULT;282 283 return 0;284 285 case PFRT_LOG_IOC_GET_DATA_INFO:286 ret = get_pfrt_log_data_info(&data_info, pfrt_log_dev);287 if (ret)288 return ret;289 290 if (copy_to_user(p, &data_info, sizeof(struct pfrt_log_data_info)))291 return -EFAULT;292 293 return 0;294 295 default:296 return -ENOTTY;297 }298}299 300static int301pfrt_log_mmap(struct file *file, struct vm_area_struct *vma)302{303 struct pfrt_log_device *pfrt_log_dev;304 struct pfrt_log_data_info info;305 unsigned long psize, vsize;306 phys_addr_t base_addr;307 int ret;308 309 if (vma->vm_flags & VM_WRITE)310 return -EROFS;311 312 /* changing from read to write with mprotect is not allowed */313 vm_flags_clear(vma, VM_MAYWRITE);314 315 pfrt_log_dev = to_pfrt_log_dev(file);316 317 ret = get_pfrt_log_data_info(&info, pfrt_log_dev);318 if (ret)319 return ret;320 321 base_addr = (phys_addr_t)((info.chunk2_addr_hi << 32) | info.chunk2_addr_lo);322 /* pfrt update has not been launched yet */323 if (!base_addr)324 return -ENODEV;325 326 psize = info.max_data_size;327 /* base address and total buffer size must be page aligned */328 if (!PAGE_ALIGNED(base_addr) || !PAGE_ALIGNED(psize))329 return -ENODEV;330 331 vsize = vma->vm_end - vma->vm_start;332 if (vsize > psize)333 return -EINVAL;334 335 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);336 if (io_remap_pfn_range(vma, vma->vm_start, PFN_DOWN(base_addr),337 vsize, vma->vm_page_prot))338 return -EAGAIN;339 340 return 0;341}342 343static const struct file_operations acpi_pfrt_log_fops = {344 .owner = THIS_MODULE,345 .mmap = pfrt_log_mmap,346 .unlocked_ioctl = pfrt_log_ioctl,347 .llseek = noop_llseek,348};349 350static void acpi_pfrt_log_remove(struct platform_device *pdev)351{352 struct pfrt_log_device *pfrt_log_dev = platform_get_drvdata(pdev);353 354 misc_deregister(&pfrt_log_dev->miscdev);355}356 357static void pfrt_log_put_idx(void *data)358{359 struct pfrt_log_device *pfrt_log_dev = data;360 361 ida_free(&pfrt_log_ida, pfrt_log_dev->index);362}363 364static int acpi_pfrt_log_probe(struct platform_device *pdev)365{366 acpi_handle handle = ACPI_HANDLE(&pdev->dev);367 struct pfrt_log_device *pfrt_log_dev;368 int ret;369 370 if (!acpi_has_method(handle, "_DSM")) {371 dev_dbg(&pdev->dev, "Missing _DSM\n");372 return -ENODEV;373 }374 375 pfrt_log_dev = devm_kzalloc(&pdev->dev, sizeof(*pfrt_log_dev), GFP_KERNEL);376 if (!pfrt_log_dev)377 return -ENOMEM;378 379 ret = ida_alloc(&pfrt_log_ida, GFP_KERNEL);380 if (ret < 0)381 return ret;382 383 pfrt_log_dev->index = ret;384 ret = devm_add_action_or_reset(&pdev->dev, pfrt_log_put_idx, pfrt_log_dev);385 if (ret)386 return ret;387 388 pfrt_log_dev->info.log_revid = PFRT_DEFAULT_REV_ID;389 pfrt_log_dev->parent_dev = &pdev->dev;390 391 pfrt_log_dev->miscdev.minor = MISC_DYNAMIC_MINOR;392 pfrt_log_dev->miscdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,393 "pfrt%d",394 pfrt_log_dev->index);395 if (!pfrt_log_dev->miscdev.name)396 return -ENOMEM;397 398 pfrt_log_dev->miscdev.nodename = devm_kasprintf(&pdev->dev, GFP_KERNEL,399 "acpi_pfr_telemetry%d",400 pfrt_log_dev->index);401 if (!pfrt_log_dev->miscdev.nodename)402 return -ENOMEM;403 404 pfrt_log_dev->miscdev.fops = &acpi_pfrt_log_fops;405 pfrt_log_dev->miscdev.parent = &pdev->dev;406 407 ret = misc_register(&pfrt_log_dev->miscdev);408 if (ret)409 return ret;410 411 platform_set_drvdata(pdev, pfrt_log_dev);412 413 return 0;414}415 416static const struct acpi_device_id acpi_pfrt_log_ids[] = {417 {"INTC1081"},418 {}419};420MODULE_DEVICE_TABLE(acpi, acpi_pfrt_log_ids);421 422static struct platform_driver acpi_pfrt_log_driver = {423 .driver = {424 .name = "pfr_telemetry",425 .acpi_match_table = acpi_pfrt_log_ids,426 },427 .probe = acpi_pfrt_log_probe,428 .remove_new = acpi_pfrt_log_remove,429};430module_platform_driver(acpi_pfrt_log_driver);431 432MODULE_DESCRIPTION("Platform Firmware Runtime Update Telemetry driver");433MODULE_LICENSE("GPL v2");434