1 // SPDX-License-Identifier: GPL-2.0
3 * ACPI Platform Firmware Runtime Telemetry driver
5 * Copyright (C) 2021 Intel Corporation
6 * Author: Chen Yu <yu.c.chen@intel.com>
8 * This driver allows user space to fetch telemetry data from the
9 * firmware with the help of the Platform Firmware Runtime Telemetry
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>
18 #include <linux/miscdevice.h>
19 #include <linux/module.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>
27 #include <uapi/linux/pfrut.h>
29 #define PFRT_LOG_EXEC_IDX 0
30 #define PFRT_LOG_HISTORY_IDX 1
32 #define PFRT_LOG_ERR 0
33 #define PFRT_LOG_WARN 1
34 #define PFRT_LOG_INFO 2
35 #define PFRT_LOG_VERB 4
37 #define PFRT_FUNC_SET_LEV 1
38 #define PFRT_FUNC_GET_LEV 2
39 #define PFRT_FUNC_GET_DATA 3
41 #define PFRT_REVID_1 1
42 #define PFRT_REVID_2 2
43 #define PFRT_DEFAULT_REV_ID PFRT_REVID_1
47 LOG_EXT_STATUS_IDX
= 1,
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,
60 struct pfrt_log_device
{
62 struct pfrt_log_info info
;
63 struct device
*parent_dev
;
64 struct miscdevice miscdev
;
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);
72 static DEFINE_IDA(pfrt_log_ida
);
74 static inline struct pfrt_log_device
*to_pfrt_log_dev(struct file
*file
)
76 return container_of(file
->private_data
, struct pfrt_log_device
, miscdev
);
79 static int get_pfrt_log_data_info(struct pfrt_log_data_info
*data_info
,
80 struct pfrt_log_device
*pfrt_log_dev
)
82 acpi_handle handle
= ACPI_HANDLE(pfrt_log_dev
->parent_dev
);
83 union acpi_object
*out_obj
, in_obj
, in_buf
;
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
;
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
);
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
;
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
;
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
;
152 static int set_pfrt_log_level(int level
, struct pfrt_log_device
*pfrt_log_dev
)
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
;
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
;
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
);
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
);
188 static int get_pfrt_log_level(struct pfrt_log_device
*pfrt_log_dev
)
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
;
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
);
201 obj
= &out_obj
->package
.elements
[0];
202 if (obj
->type
!= ACPI_TYPE_INTEGER
)
203 goto free_acpi_buffer
;
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
;
214 obj
= &out_obj
->package
.elements
[2];
215 if (obj
->type
!= ACPI_TYPE_INTEGER
)
216 goto free_acpi_buffer
;
218 ret
= obj
->integer
.value
;
226 static int valid_log_level(u32 level
)
228 return level
== PFRT_LOG_ERR
|| level
== PFRT_LOG_WARN
||
229 level
== PFRT_LOG_INFO
|| level
== PFRT_LOG_VERB
;
232 static int valid_log_type(u32 type
)
234 return type
== PFRT_LOG_EXEC_IDX
|| type
== PFRT_LOG_HISTORY_IDX
;
237 static inline int valid_log_revid(u32 id
)
239 return id
== PFRT_REVID_1
|| id
== PFRT_REVID_2
;
242 static long pfrt_log_ioctl(struct file
*file
, unsigned int cmd
, unsigned long arg
)
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
;
250 p
= (void __user
*)arg
;
253 case PFRT_LOG_IOC_SET_INFO
:
254 if (copy_from_user(&info
, p
, sizeof(info
)))
257 if (valid_log_revid(info
.log_revid
))
258 pfrt_log_dev
->info
.log_revid
= info
.log_revid
;
260 if (valid_log_level(info
.log_level
)) {
261 ret
= set_pfrt_log_level(info
.log_level
, pfrt_log_dev
);
265 pfrt_log_dev
->info
.log_level
= info
.log_level
;
268 if (valid_log_type(info
.log_type
))
269 pfrt_log_dev
->info
.log_type
= info
.log_type
;
273 case PFRT_LOG_IOC_GET_INFO
:
274 info
.log_level
= get_pfrt_log_level(pfrt_log_dev
);
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
)))
285 case PFRT_LOG_IOC_GET_DATA_INFO
:
286 ret
= get_pfrt_log_data_info(&data_info
, pfrt_log_dev
);
290 if (copy_to_user(p
, &data_info
, sizeof(struct pfrt_log_data_info
)))
301 pfrt_log_mmap(struct file
*file
, struct vm_area_struct
*vma
)
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
;
309 if (vma
->vm_flags
& VM_WRITE
)
312 /* changing from read to write with mprotect is not allowed */
313 vm_flags_clear(vma
, VM_MAYWRITE
);
315 pfrt_log_dev
= to_pfrt_log_dev(file
);
317 ret
= get_pfrt_log_data_info(&info
, pfrt_log_dev
);
321 base_addr
= (phys_addr_t
)((info
.chunk2_addr_hi
<< 32) | info
.chunk2_addr_lo
);
322 /* pfrt update has not been launched yet */
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
))
331 vsize
= vma
->vm_end
- vma
->vm_start
;
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
))
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
,
350 static void acpi_pfrt_log_remove(struct platform_device
*pdev
)
352 struct pfrt_log_device
*pfrt_log_dev
= platform_get_drvdata(pdev
);
354 misc_deregister(&pfrt_log_dev
->miscdev
);
357 static void pfrt_log_put_idx(void *data
)
359 struct pfrt_log_device
*pfrt_log_dev
= data
;
361 ida_free(&pfrt_log_ida
, pfrt_log_dev
->index
);
364 static int acpi_pfrt_log_probe(struct platform_device
*pdev
)
366 acpi_handle handle
= ACPI_HANDLE(&pdev
->dev
);
367 struct pfrt_log_device
*pfrt_log_dev
;
370 if (!acpi_has_method(handle
, "_DSM")) {
371 dev_dbg(&pdev
->dev
, "Missing _DSM\n");
375 pfrt_log_dev
= devm_kzalloc(&pdev
->dev
, sizeof(*pfrt_log_dev
), GFP_KERNEL
);
379 ret
= ida_alloc(&pfrt_log_ida
, GFP_KERNEL
);
383 pfrt_log_dev
->index
= ret
;
384 ret
= devm_add_action_or_reset(&pdev
->dev
, pfrt_log_put_idx
, pfrt_log_dev
);
388 pfrt_log_dev
->info
.log_revid
= PFRT_DEFAULT_REV_ID
;
389 pfrt_log_dev
->parent_dev
= &pdev
->dev
;
391 pfrt_log_dev
->miscdev
.minor
= MISC_DYNAMIC_MINOR
;
392 pfrt_log_dev
->miscdev
.name
= devm_kasprintf(&pdev
->dev
, GFP_KERNEL
,
394 pfrt_log_dev
->index
);
395 if (!pfrt_log_dev
->miscdev
.name
)
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
)
404 pfrt_log_dev
->miscdev
.fops
= &acpi_pfrt_log_fops
;
405 pfrt_log_dev
->miscdev
.parent
= &pdev
->dev
;
407 ret
= misc_register(&pfrt_log_dev
->miscdev
);
411 platform_set_drvdata(pdev
, pfrt_log_dev
);
416 static const struct acpi_device_id acpi_pfrt_log_ids
[] = {
420 MODULE_DEVICE_TABLE(acpi
, acpi_pfrt_log_ids
);
422 static struct platform_driver acpi_pfrt_log_driver
= {
424 .name
= "pfr_telemetry",
425 .acpi_match_table
= acpi_pfrt_log_ids
,
427 .probe
= acpi_pfrt_log_probe
,
428 .remove_new
= acpi_pfrt_log_remove
,
430 module_platform_driver(acpi_pfrt_log_driver
);
432 MODULE_DESCRIPTION("Platform Firmware Runtime Update Telemetry driver");
433 MODULE_LICENSE("GPL v2");