1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * ACPI Platform Firmware Runtime Telemetry driver 4 * 5 * Copyright (C) 2021 Intel Corporation 6 * Author: Chen Yu <yu.c.chen@intel.com> 7 * 8 * This driver allows user space to fetch telemetry data from the 9 * firmware with the help of the Platform Firmware Runtime Telemetry 10 * 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 0 30 #define PFRT_LOG_HISTORY_IDX 1 31 32 #define PFRT_LOG_ERR 0 33 #define PFRT_LOG_WARN 1 34 #define PFRT_LOG_INFO 2 35 #define PFRT_LOG_VERB 4 36 37 #define PFRT_FUNC_SET_LEV 1 38 #define PFRT_FUNC_GET_LEV 2 39 #define PFRT_FUNC_GET_DATA 3 40 41 #define PFRT_REVID_1 1 42 #define PFRT_REVID_2 2 43 #define PFRT_DEFAULT_REV_ID PFRT_REVID_1 44 45 enum 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_IDX 58 }; 59 60 struct 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 */ 68 static const guid_t pfrt_log_guid = 69 GUID_INIT(0x75191659, 0x8178, 0x4D9D, 0xB8, 0x8F, 0xAC, 0x5E, 70 0x5E, 0x93, 0xE8, 0xBF); 71 72 static DEFINE_IDA(pfrt_log_ida); 73 74 static 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 79 static 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 146 free_acpi_buffer: 147 ACPI_FREE(out_obj); 148 149 return ret; 150 } 151 152 static 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 188 static 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 220 free_acpi_buffer: 221 ACPI_FREE(out_obj); 222 223 return ret; 224 } 225 226 static 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 232 static int valid_log_type(u32 type) 233 { 234 return type == PFRT_LOG_EXEC_IDX || type == PFRT_LOG_HISTORY_IDX; 235 } 236 237 static inline int valid_log_revid(u32 id) 238 { 239 return id == PFRT_REVID_1 || id == PFRT_REVID_2; 240 } 241 242 static 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 300 static int 301 pfrt_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 vma->vm_flags &= ~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 343 static 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 350 static int 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 return 0; 357 } 358 359 static void pfrt_log_put_idx(void *data) 360 { 361 struct pfrt_log_device *pfrt_log_dev = data; 362 363 ida_free(&pfrt_log_ida, pfrt_log_dev->index); 364 } 365 366 static int acpi_pfrt_log_probe(struct platform_device *pdev) 367 { 368 acpi_handle handle = ACPI_HANDLE(&pdev->dev); 369 struct pfrt_log_device *pfrt_log_dev; 370 int ret; 371 372 if (!acpi_has_method(handle, "_DSM")) { 373 dev_dbg(&pdev->dev, "Missing _DSM\n"); 374 return -ENODEV; 375 } 376 377 pfrt_log_dev = devm_kzalloc(&pdev->dev, sizeof(*pfrt_log_dev), GFP_KERNEL); 378 if (!pfrt_log_dev) 379 return -ENOMEM; 380 381 ret = ida_alloc(&pfrt_log_ida, GFP_KERNEL); 382 if (ret < 0) 383 return ret; 384 385 pfrt_log_dev->index = ret; 386 ret = devm_add_action_or_reset(&pdev->dev, pfrt_log_put_idx, pfrt_log_dev); 387 if (ret) 388 return ret; 389 390 pfrt_log_dev->info.log_revid = PFRT_DEFAULT_REV_ID; 391 pfrt_log_dev->parent_dev = &pdev->dev; 392 393 pfrt_log_dev->miscdev.minor = MISC_DYNAMIC_MINOR; 394 pfrt_log_dev->miscdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, 395 "pfrt%d", 396 pfrt_log_dev->index); 397 if (!pfrt_log_dev->miscdev.name) 398 return -ENOMEM; 399 400 pfrt_log_dev->miscdev.nodename = devm_kasprintf(&pdev->dev, GFP_KERNEL, 401 "acpi_pfr_telemetry%d", 402 pfrt_log_dev->index); 403 if (!pfrt_log_dev->miscdev.nodename) 404 return -ENOMEM; 405 406 pfrt_log_dev->miscdev.fops = &acpi_pfrt_log_fops; 407 pfrt_log_dev->miscdev.parent = &pdev->dev; 408 409 ret = misc_register(&pfrt_log_dev->miscdev); 410 if (ret) 411 return ret; 412 413 platform_set_drvdata(pdev, pfrt_log_dev); 414 415 return 0; 416 } 417 418 static const struct acpi_device_id acpi_pfrt_log_ids[] = { 419 {"INTC1081"}, 420 {} 421 }; 422 MODULE_DEVICE_TABLE(acpi, acpi_pfrt_log_ids); 423 424 static struct platform_driver acpi_pfrt_log_driver = { 425 .driver = { 426 .name = "pfr_telemetry", 427 .acpi_match_table = acpi_pfrt_log_ids, 428 }, 429 .probe = acpi_pfrt_log_probe, 430 .remove = acpi_pfrt_log_remove, 431 }; 432 module_platform_driver(acpi_pfrt_log_driver); 433 434 MODULE_DESCRIPTION("Platform Firmware Runtime Update Telemetry driver"); 435 MODULE_LICENSE("GPL v2"); 436