1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Intel PMC PWRM ACPI driver
4 *
5 * Copyright (C) 2025, Intel Corporation
6 */
7
8 #include <linux/acpi.h>
9 #include <linux/bits.h>
10 #include <linux/bitfield.h>
11 #include <linux/cleanup.h>
12 #include <linux/device.h>
13 #include <linux/err.h>
14 #include <linux/errno.h>
15 #include <linux/intel_vsec.h>
16 #include <linux/limits.h>
17 #include <linux/module.h>
18 #include <linux/platform_device.h>
19 #include <linux/resource.h>
20 #include <linux/slab.h>
21 #include <linux/types.h>
22 #include <linux/uuid.h>
23
24 #include "core.h"
25
26 #define ENTRY_LEN 5
27
28 /* DWORD2 */
29 #define DVSEC_ID_MASK GENMASK(15, 0)
30 #define NUM_ENTRIES_MASK GENMASK(23, 16)
31 #define ENTRY_SIZE_MASK GENMASK(31, 24)
32
33 /* DWORD3 */
34 #define TBIR_MASK GENMASK(2, 0)
35 #define DISC_TBL_OFF_MASK GENMASK(31, 3)
36
37 const guid_t intel_vsec_guid =
38 GUID_INIT(0x294903fb, 0x634d, 0x4fc7, 0xaf, 0x1f, 0x0f, 0xb9,
39 0x56, 0xb0, 0x4f, 0xc1);
40
is_valid_entry(union acpi_object * pkg)41 static bool is_valid_entry(union acpi_object *pkg)
42 {
43 int i;
44
45 if (!pkg || pkg->type != ACPI_TYPE_PACKAGE || pkg->package.count != ENTRY_LEN)
46 return false;
47
48 if (pkg->package.elements[0].type != ACPI_TYPE_STRING)
49 return false;
50
51 for (i = 1; i < ENTRY_LEN; i++)
52 if (pkg->package.elements[i].type != ACPI_TYPE_INTEGER)
53 return false;
54
55 return true;
56 }
57
pmc_parse_telem_dsd(union acpi_object * obj,struct intel_vsec_header * header)58 acpi_disc_t pmc_parse_telem_dsd(union acpi_object *obj,
59 struct intel_vsec_header *header)
60 {
61 union acpi_object *vsec_pkg;
62 union acpi_object *disc_pkg;
63 u64 hdr0, hdr1;
64 int num_regions;
65 int i;
66
67 if (!header)
68 return ERR_PTR(-EINVAL);
69
70 if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2)
71 return ERR_PTR(-EINVAL);
72
73 /* First Package is DVSEC info */
74 vsec_pkg = &obj->package.elements[0];
75 if (!is_valid_entry(vsec_pkg))
76 return ERR_PTR(-EINVAL);
77
78 hdr0 = vsec_pkg->package.elements[3].integer.value;
79 hdr1 = vsec_pkg->package.elements[4].integer.value;
80
81 header->id = FIELD_GET(DVSEC_ID_MASK, hdr0);
82 header->num_entries = FIELD_GET(NUM_ENTRIES_MASK, hdr0);
83 header->entry_size = FIELD_GET(ENTRY_SIZE_MASK, hdr0);
84 header->tbir = FIELD_GET(TBIR_MASK, hdr1);
85 header->offset = hdr1 & DISC_TBL_OFF_MASK;
86
87 /* Second Package contains the discovery tables */
88 disc_pkg = &obj->package.elements[1];
89 if (disc_pkg->type != ACPI_TYPE_PACKAGE || disc_pkg->package.count < 1)
90 return ERR_PTR(-EINVAL);
91
92 num_regions = disc_pkg->package.count;
93 if (header->num_entries != num_regions)
94 return ERR_PTR(-EINVAL);
95
96 acpi_disc_t disc __free(kfree) = kmalloc_array(num_regions, sizeof(*disc),
97 GFP_KERNEL);
98 if (!disc)
99 return ERR_PTR(-ENOMEM);
100
101 for (i = 0; i < num_regions; i++) {
102 union acpi_object *pkg;
103 u64 value;
104 int j;
105
106 pkg = &disc_pkg->package.elements[i];
107 if (!is_valid_entry(pkg))
108 return ERR_PTR(-EINVAL);
109
110 /* Element 0 is a descriptive string; DWORD values start at index 1. */
111 for (j = 1; j < ENTRY_LEN; j++) {
112 value = pkg->package.elements[j].integer.value;
113 if (value > U32_MAX)
114 return ERR_PTR(-ERANGE);
115
116 disc[i][j - 1] = value;
117 }
118 }
119
120 return no_free_ptr(disc);
121 }
122 EXPORT_SYMBOL_NS_GPL(pmc_parse_telem_dsd, "INTEL_PMC_CORE");
123
pmc_find_telem_guid(union acpi_object * dsd)124 union acpi_object *pmc_find_telem_guid(union acpi_object *dsd)
125 {
126 int i;
127
128 if (!dsd || dsd->type != ACPI_TYPE_PACKAGE)
129 return NULL;
130
131 for (i = 0; i + 1 < dsd->package.count; i += 2) {
132 union acpi_object *uuid_obj, *data_obj;
133 guid_t uuid;
134
135 uuid_obj = &dsd->package.elements[i];
136 data_obj = &dsd->package.elements[i + 1];
137
138 if (uuid_obj->type != ACPI_TYPE_BUFFER ||
139 uuid_obj->buffer.length != 16)
140 continue;
141
142 memcpy(&uuid, uuid_obj->buffer.pointer, 16);
143 if (guid_equal(&uuid, &intel_vsec_guid))
144 return data_obj;
145 }
146
147 return NULL;
148 }
149 EXPORT_SYMBOL_NS_GPL(pmc_find_telem_guid, "INTEL_PMC_CORE");
150
pmc_pwrm_acpi_probe(struct platform_device * pdev)151 static int pmc_pwrm_acpi_probe(struct platform_device *pdev)
152 {
153 struct intel_vsec_header header;
154 struct intel_vsec_header *headers[2] = { &header, NULL };
155 struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
156 struct intel_vsec_platform_info info = { };
157 struct device *dev = &pdev->dev;
158 union acpi_object *dsd;
159 struct resource *res;
160 acpi_handle handle;
161 acpi_status status;
162
163 handle = ACPI_HANDLE(&pdev->dev);
164 if (!handle)
165 return -ENODEV;
166
167 status = acpi_evaluate_object(handle, "_DSD", NULL, &buf);
168 if (ACPI_FAILURE(status)) {
169 return dev_err_probe(dev, -ENODEV, "Could not evaluate _DSD: %s\n",
170 acpi_format_exception(status));
171 }
172
173 void *dsd_buf __free(pmc_acpi_free) = buf.pointer;
174
175 dsd = pmc_find_telem_guid(dsd_buf);
176 if (!dsd)
177 return -ENODEV;
178
179 acpi_disc_t acpi_disc __free(kfree) = pmc_parse_telem_dsd(dsd, &header);
180 if (IS_ERR(acpi_disc))
181 return PTR_ERR(acpi_disc);
182
183 res = platform_get_resource(pdev, IORESOURCE_MEM, header.tbir);
184 if (!res)
185 return -EINVAL;
186
187 info.headers = headers;
188 info.caps = VSEC_CAP_TELEMETRY;
189 info.acpi_disc = acpi_disc;
190 info.src = INTEL_VSEC_DISC_ACPI;
191 info.base_addr = res->start;
192
193 return intel_vsec_register(&pdev->dev, &info);
194 }
195
196 static const struct acpi_device_id pmc_pwrm_acpi_ids[] = {
197 { "INTC1122", 0 }, /* Nova Lake */
198 { "INTC1129", 0 }, /* Nova Lake */
199 { }
200 };
201 MODULE_DEVICE_TABLE(acpi, pmc_pwrm_acpi_ids);
202
203 static struct platform_driver pmc_pwrm_acpi_driver = {
204 .probe = pmc_pwrm_acpi_probe,
205 .driver = {
206 .name = "intel_pmc_pwrm_acpi",
207 .acpi_match_table = ACPI_PTR(pmc_pwrm_acpi_ids),
208 },
209 };
210 module_platform_driver(pmc_pwrm_acpi_driver);
211
212 MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
213 MODULE_DESCRIPTION("Intel PMC PWRM ACPI driver");
214 MODULE_LICENSE("GPL");
215 MODULE_IMPORT_NS("INTEL_VSEC");
216