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
);
275 info
.log_type
= pfrt_log_dev
->info
.log_type
;
276 info
.log_revid
= pfrt_log_dev
->info
.log_revid
;
277 if (copy_to_user(p
, &info
, sizeof(info
)))
282 case PFRT_LOG_IOC_GET_DATA_INFO
:
283 ret
= get_pfrt_log_data_info(&data_info
, pfrt_log_dev
);
287 if (copy_to_user(p
, &data_info
, sizeof(struct pfrt_log_data_info
)))
298 pfrt_log_mmap(struct file
*file
, struct vm_area_struct
*vma
)
300 struct pfrt_log_device
*pfrt_log_dev
;
301 struct pfrt_log_data_info info
;
302 unsigned long psize
, vsize
;
303 phys_addr_t base_addr
;
306 if (vma
->vm_flags
& VM_WRITE
)
309 /* changing from read to write with mprotect is not allowed */
310 vm_flags_clear(vma
, VM_MAYWRITE
);
312 pfrt_log_dev
= to_pfrt_log_dev(file
);
314 ret
= get_pfrt_log_data_info(&info
, pfrt_log_dev
);
318 base_addr
= (phys_addr_t
)((info
.chunk2_addr_hi
<< 32) | info
.chunk2_addr_lo
);
319 /* pfrt update has not been launched yet */
323 psize
= info
.max_data_size
;
324 /* base address and total buffer size must be page aligned */
325 if (!PAGE_ALIGNED(base_addr
) || !PAGE_ALIGNED(psize
))
328 vsize
= vma
->vm_end
- vma
->vm_start
;
332 vma
->vm_page_prot
= pgprot_noncached(vma
->vm_page_prot
);
333 if (io_remap_pfn_range(vma
, vma
->vm_start
, PFN_DOWN(base_addr
),
334 vsize
, vma
->vm_page_prot
))
340 static const struct file_operations acpi_pfrt_log_fops
= {
341 .owner
= THIS_MODULE
,
342 .mmap
= pfrt_log_mmap
,
343 .unlocked_ioctl
= pfrt_log_ioctl
,
344 .llseek
= noop_llseek
,
347 static void acpi_pfrt_log_remove(struct platform_device
*pdev
)
349 struct pfrt_log_device
*pfrt_log_dev
= platform_get_drvdata(pdev
);
351 misc_deregister(&pfrt_log_dev
->miscdev
);
354 static void pfrt_log_put_idx(void *data
)
356 struct pfrt_log_device
*pfrt_log_dev
= data
;
358 ida_free(&pfrt_log_ida
, pfrt_log_dev
->index
);
361 static int acpi_pfrt_log_probe(struct platform_device
*pdev
)
363 acpi_handle handle
= ACPI_HANDLE(&pdev
->dev
);
364 struct pfrt_log_device
*pfrt_log_dev
;
367 if (!acpi_has_method(handle
, "_DSM")) {
368 dev_dbg(&pdev
->dev
, "Missing _DSM\n");
372 pfrt_log_dev
= devm_kzalloc(&pdev
->dev
, sizeof(*pfrt_log_dev
), GFP_KERNEL
);
376 ret
= ida_alloc(&pfrt_log_ida
, GFP_KERNEL
);
380 pfrt_log_dev
->index
= ret
;
381 ret
= devm_add_action_or_reset(&pdev
->dev
, pfrt_log_put_idx
, pfrt_log_dev
);
385 pfrt_log_dev
->info
.log_revid
= PFRT_DEFAULT_REV_ID
;
386 pfrt_log_dev
->parent_dev
= &pdev
->dev
;
388 pfrt_log_dev
->miscdev
.minor
= MISC_DYNAMIC_MINOR
;
389 pfrt_log_dev
->miscdev
.name
= devm_kasprintf(&pdev
->dev
, GFP_KERNEL
,
391 pfrt_log_dev
->index
);
392 if (!pfrt_log_dev
->miscdev
.name
)
395 pfrt_log_dev
->miscdev
.nodename
= devm_kasprintf(&pdev
->dev
, GFP_KERNEL
,
396 "acpi_pfr_telemetry%d",
397 pfrt_log_dev
->index
);
398 if (!pfrt_log_dev
->miscdev
.nodename
)
401 pfrt_log_dev
->miscdev
.fops
= &acpi_pfrt_log_fops
;
402 pfrt_log_dev
->miscdev
.parent
= &pdev
->dev
;
404 ret
= misc_register(&pfrt_log_dev
->miscdev
);
408 platform_set_drvdata(pdev
, pfrt_log_dev
);
413 static const struct acpi_device_id acpi_pfrt_log_ids
[] = {
417 MODULE_DEVICE_TABLE(acpi
, acpi_pfrt_log_ids
);
419 static struct platform_driver acpi_pfrt_log_driver
= {
421 .name
= "pfr_telemetry",
422 .acpi_match_table
= acpi_pfrt_log_ids
,
424 .probe
= acpi_pfrt_log_probe
,
425 .remove
= acpi_pfrt_log_remove
,
427 module_platform_driver(acpi_pfrt_log_driver
);
429 MODULE_DESCRIPTION("Platform Firmware Runtime Update Telemetry driver");
430 MODULE_LICENSE("GPL v2");