summaryrefslogtreecommitdiffstats
path: root/drivers/acpi/prmt.c
blob: 1f6007abcf18eda3259ae274d08a6b1f48cc5dde (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Author: Erik Kaneda <erik.kaneda@intel.com>
 * Copyright 2020 Intel Corporation
 *
 * prmt.c
 *
 * Each PRM service is an executable that is run in a restricted environment
 * that is invoked by writing to the PlatformRtMechanism OperationRegion from
 * AML bytecode.
 *
 * init_prmt initializes the Platform Runtime Mechanism (PRM) services by
 * processing data in the PRMT as well as registering an ACPI OperationRegion
 * handler for the PlatformRtMechanism subtype.
 *
 */
#include <linux/kernel.h>
#include <linux/efi.h>
#include <linux/acpi.h>
#include <linux/prmt.h>
#include <asm/efi.h>

#pragma pack(1)
struct prm_mmio_addr_range {
	u64 phys_addr;
	u64 virt_addr;
	u32 length;
};

struct prm_mmio_info {
	u64 mmio_count;
	struct prm_mmio_addr_range addr_ranges[];
};

struct prm_buffer {
	u8 prm_status;
	u64 efi_status;
	u8 prm_cmd;
	guid_t handler_guid;
};

struct prm_context_buffer {
	char signature[ACPI_NAMESEG_SIZE];
	u16 revision;
	u16 reserved;
	guid_t identifier;
	u64 static_data_buffer;
	struct prm_mmio_info *mmio_ranges;
};
#pragma pack()


static LIST_HEAD(prm_module_list);

struct prm_handler_info {
	guid_t guid;
	u64 handler_addr;
	u64 static_data_buffer_addr;
	u64 acpi_param_buffer_addr;

	struct list_head handler_list;
};

struct prm_module_info {
	guid_t guid;
	u16 major_rev;
	u16 minor_rev;
	u16 handler_count;
	struct prm_mmio_info *mmio_info;
	bool updatable;

	struct list_head module_list;
	struct prm_handler_info handlers[];
};


static u64 efi_pa_va_lookup(u64 pa)
{
	efi_memory_desc_t *md;
	u64 pa_offset = pa & ~PAGE_MASK;
	u64 page = pa & PAGE_MASK;

	for_each_efi_memory_desc(md) {
		if (md->phys_addr < pa && pa < md->phys_addr + PAGE_SIZE * md->num_pages)
			return pa_offset + md->virt_addr + page - md->phys_addr;
	}

	return 0;
}


#define get_first_handler(a) ((struct acpi_prmt_handler_info *) ((char *) (a) + a->handler_info_offset))
#define get_next_handler(a) ((struct acpi_prmt_handler_info *) (sizeof(struct acpi_prmt_handler_info) + (char *) a))

static int __init
acpi_parse_prmt(union acpi_subtable_headers *header, const unsigned long end)
{
	struct acpi_prmt_module_info *module_info;
	struct acpi_prmt_handler_info *handler_info;
	struct prm_handler_info *th;
	struct prm_module_info *tm;
	u64 mmio_count = 0;
	u64 cur_handler = 0;
	u32 module_info_size = 0;
	u64 mmio_range_size = 0;
	void *temp_mmio;

	module_info = (struct acpi_prmt_module_info *) header;
	module_info_size = struct_size(tm, handlers, module_info->handler_info_count);
	tm = kmalloc(module_info_size, GFP_KERNEL);

	guid_copy(&tm->guid, (guid_t *) module_info->module_guid);
	tm->major_rev = module_info->major_rev;
	tm->minor_rev = module_info->minor_rev;
	tm->handler_count = module_info->handler_info_count;
	tm->updatable = true;

	if (module_info->mmio_list_pointer) {
		/*
		 * Each module is associated with a list of addr
		 * ranges that it can use during the service
		 */
		mmio_count = *(u64 *) memremap(module_info->mmio_list_pointer, 8, MEMREMAP_WB);
		mmio_range_size = struct_size(tm->mmio_info, addr_ranges, mmio_count);
		tm->mmio_info = kmalloc(mmio_range_size, GFP_KERNEL);
		temp_mmio = memremap(module_info->mmio_list_pointer, mmio_range_size, MEMREMAP_WB);
		memmove(tm->mmio_info, temp_mmio, mmio_range_size);
	} else {
		mmio_range_size = struct_size(tm->mmio_info, addr_ranges, mmio_count);
		tm->mmio_info = kmalloc(mmio_range_size, GFP_KERNEL);
		tm->mmio_info->mmio_count = 0;
	}

	INIT_LIST_HEAD(&tm->module_list);
	list_add(&tm->module_list, &prm_module_list);

	handler_info = get_first_handler(module_info);
	do {
		th = &tm->handlers[cur_handler];

		guid_copy(&th->guid, (guid_t *)handler_info->handler_guid);
		th->handler_addr = efi_pa_va_lookup(handler_info->handler_address);
		th->static_data_buffer_addr = efi_pa_va_lookup(handler_info->static_data_buffer_address);
		th->acpi_param_buffer_addr = efi_pa_va_lookup(handler_info->acpi_param_buffer_address);
	} while (++cur_handler < tm->handler_count && (handler_info = get_next_handler(handler_info)));

	return 0;
}

#define GET_MODULE	0
#define GET_HANDLER	1

static void *find_guid_info(const guid_t *guid, u8 mode)
{
	struct prm_handler_info *cur_handler;
	struct prm_module_info *cur_module;
	int i = 0;

	list_for_each_entry(cur_module, &prm_module_list, module_list) {
		for (i = 0; i < cur_module->handler_count; ++i) {
			cur_handler = &cur_module->handlers[i];
			if (guid_equal(guid, &cur_handler->guid)) {
				if (mode == GET_MODULE)
					return (void *)cur_module;
				else
					return (void *)cur_handler;
			}
		}
	}

	return NULL;
}


static struct prm_module_info *find_prm_module(const guid_t *guid)
{
	return (struct prm_module_info *)find_guid_info(guid, GET_MODULE);
}

static struct prm_handler_info *find_prm_handler(const guid_t *guid)
{
	return (struct prm_handler_info *) find_guid_info(guid, GET_HANDLER);
}

/* In-coming PRM commands */

#define PRM_CMD_RUN_SERVICE		0
#define PRM_CMD_START_TRANSACTION	1
#define PRM_CMD_END_TRANSACTION		2

/* statuses that can be passed back to ASL */

#define PRM_HANDLER_SUCCESS 		0
#define PRM_HANDLER_ERROR 		1
#define INVALID_PRM_COMMAND 		2
#define PRM_HANDLER_GUID_NOT_FOUND 	3
#define UPDATE_LOCK_ALREADY_HELD 	4
#define UPDATE_UNLOCK_WITHOUT_LOCK 	5

/*
 * This is the PlatformRtMechanism opregion space handler.
 * @function: indicates the read/write. In fact as the PlatformRtMechanism
 * message is driven by command, only write is meaningful.
 *
 * @addr   : not used
 * @bits   : not used.
 * @value  : it is an in/out parameter. It points to the PRM message buffer.
 * @handler_context: not used
 */
static acpi_status acpi_platformrt_space_handler(u32 function,
						 acpi_physical_address addr,
						 u32 bits, acpi_integer *value,
						 void *handler_context,
						 void *region_context)
{
	struct prm_buffer *buffer = ACPI_CAST_PTR(struct prm_buffer, value);
	struct prm_handler_info *handler;
	struct prm_module_info *module;
	efi_status_t status;
	struct prm_context_buffer context;

	/*
	 * The returned acpi_status will always be AE_OK. Error values will be
	 * saved in the first byte of the PRM message buffer to be used by ASL.
	 */
	switch (buffer->prm_cmd) {
	case PRM_CMD_RUN_SERVICE:

		handler = find_prm_handler(&buffer->handler_guid);
		module = find_prm_module(&buffer->handler_guid);
		if (!handler || !module)
			goto invalid_guid;

		ACPI_COPY_NAMESEG(context.signature, "PRMC");
		context.revision = 0x0;
		context.reserved = 0x0;
		context.identifier = handler->guid;
		context.static_data_buffer = handler->static_data_buffer_addr;
		context.mmio_ranges = module->mmio_info;

		status = efi_call_virt_pointer(handler, handler_addr,
					       handler->acpi_param_buffer_addr,
					       &context);
		if (status == EFI_SUCCESS) {
			buffer->prm_status = PRM_HANDLER_SUCCESS;
		} else {
			buffer->prm_status = PRM_HANDLER_ERROR;
			buffer->efi_status = status;
		}
		break;

	case PRM_CMD_START_TRANSACTION:

		module = find_prm_module(&buffer->handler_guid);
		if (!module)
			goto invalid_guid;

		if (module->updatable)
			module->updatable = false;
		else
			buffer->prm_status = UPDATE_LOCK_ALREADY_HELD;
		break;

	case PRM_CMD_END_TRANSACTION:

		module = find_prm_module(&buffer->handler_guid);
		if (!module)
			goto invalid_guid;

		if (module->updatable)
			buffer->prm_status = UPDATE_UNLOCK_WITHOUT_LOCK;
		else
			module->updatable = true;
		break;

	default:

		buffer->prm_status = INVALID_PRM_COMMAND;
		break;
	}

	return AE_OK;

invalid_guid:
	buffer->prm_status = PRM_HANDLER_GUID_NOT_FOUND;
	return AE_OK;
}

void __init init_prmt(void)
{
	acpi_status status;
	int mc = acpi_table_parse_entries(ACPI_SIG_PRMT, sizeof(struct acpi_table_prmt) +
					  sizeof (struct acpi_table_prmt_header),
					  0, acpi_parse_prmt, 0);
	/*
	 * Return immediately if PRMT table is not present or no PRM module found.
	 */
	if (mc <= 0)
		return;

	pr_info("PRM: found %u modules\n", mc);

	status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
						    ACPI_ADR_SPACE_PLATFORM_RT,
						    &acpi_platformrt_space_handler,
						    NULL, NULL);
	if (ACPI_FAILURE(status))
		pr_alert("PRM: OperationRegion handler could not be installed\n");
}