xref: /optee_os/core/drivers/amd/asu/asu_main.c (revision 7f2d4e10736f698f6b7739a9cd39e64d96c98a0a)
1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3  * Copyright (c) 2025-2026, Advanced Micro Devices, Inc. All rights reserved.
4  *
5  */
6 
7 #include <assert.h>
8 #include <drivers/amd/asu_client.h>
9 #include <initcall.h>
10 #include <io.h>
11 #include <kernel/delay.h>
12 #include <kernel/dt.h>
13 #include <kernel/interrupt.h>
14 #include <kernel/panic.h>
15 #include <kernel/spinlock.h>
16 #include <libfdt.h>
17 #include <mm/core_mmu.h>
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <string.h>
21 #include <trace.h>
22 #include <util.h>
23 
24 #include "asu_doorbell.h"
25 
26 #define ASU_QUEUE_BUFFER_FULL		0xFFU
27 #define ASU_CLIENT_READY		0xFFFFFFFFU
28 #define ASU_TARGET_IPI_INT_MASK		1U
29 
30 #define ASU_BASEADDR			0xEBF80000U
31 #define ASU_GLOBAL_CNTRL		(ASU_BASEADDR + 0x00000000U)
32 
33 #define ASU_BASEADDR_SIZE		0x10000U
34 #define ASU_GLOBAL_ADDR_LIMIT		0x1000U
35 
36 #define ASU_GLOBAL_CNTRL_FW_IS_PRESENT_MASK	0x10U
37 #define ASU_ASUFW_BIT_CHECK_TIMEOUT_VALUE	0xFFFFFU
38 
39 #define ASU_CHNL_IPI_BITMASK		GENMASK_32(31, 16)
40 
41 struct asu_client {
42 	struct asu_channel_memory *chnl_memptr;
43 	uint32_t is_ready;
44 	unsigned int slock;          /* chnl_memptr spin lock */
45 	void *global_ctrl;
46 	void *doorbell;
47 };
48 
49 struct asu_ids {
50 	uint8_t ids[ASU_UNIQUE_ID_MAX];
51 	unsigned int slock;          /* id array spin lock */
52 };
53 
54 static struct asu_ids asuid;
55 static struct asu_client *asu;
56 
57 /*
58  * asu_fwcheck() - Check if ASU firmware is present and ready
59  * Polls the ASU global control register to verify if the HSM firmware
60  * is present and ready for interaction. Uses a timeout of approximately
61  * 1 second for the check.
62  *
63  * Return: TEE_SUCCESS if firmware is ready, TEE_ERROR_BAD_STATE if not present
64  */
asu_fwcheck(void)65 static TEE_Result asu_fwcheck(void)
66 {
67 	uint64_t timeout = 0;
68 
69 	/*
70 	 * Timeout is set to ~1sec.
71 	 * This is the worst case time within which ASUFW ready for interaction
72 	 * with components requests.
73 	 */
74 	timeout = timeout_init_us(ASU_ASUFW_BIT_CHECK_TIMEOUT_VALUE);
75 
76 	do {
77 		if (io_read32((vaddr_t)asu->global_ctrl) &
78 		    ASU_GLOBAL_CNTRL_FW_IS_PRESENT_MASK) {
79 			DMSG("ASU FW is ready!");
80 			return TEE_SUCCESS;
81 		}
82 	} while (!timeout_elapsed(timeout));
83 
84 	EMSG("ASU FW is not present!");
85 
86 	return TEE_ERROR_BAD_STATE;
87 }
88 
89 /*
90  * asu_get_channelID() - Determine the ASU channel ID for APU communication
91  *
92  * Maps the Runtime Configuration Area (RTCA) to find which ASU channel
93  * should be used by the APU for communication. Searches through available
94  * channels to find one matching the APU's local IPI ID.
95  *
96  * Return: Channel ID (0 to ASU_MAX_IPI_CHANNELS-1) or ASU_MAX_IPI_CHANNELS on
97  *	   failure
98  */
asu_get_channelID(void)99 static uint32_t asu_get_channelID(void)
100 {
101 	TEE_Result ret = TEE_ERROR_GENERIC;
102 	void *comm_chnl_info = NULL;
103 	uint32_t channel_id = ASU_MAX_IPI_CHANNELS;
104 	vaddr_t membase = 0;
105 	uint32_t id = 0;
106 
107 	/*
108 	 * RTCA is mapped only to find the ASU Channel ID
109 	 * to be used by APU.
110 	 * After reading the information RTCA region is unmapped.
111 	 */
112 	comm_chnl_info = core_mmu_add_mapping(MEM_AREA_IO_SEC,
113 					      ASU_RTCA_BASEADDR,
114 					      ASU_GLOBAL_ADDR_LIMIT);
115 	if (!comm_chnl_info) {
116 		EMSG("Failed to map runtime config area");
117 		return channel_id;
118 	}
119 
120 	for (id = 0; id < ASU_MAX_IPI_CHANNELS; id++) {
121 		membase = (vaddr_t)comm_chnl_info +
122 			  ASU_RTCA_CHANNEL_BASE_OFFSET +
123 			  ASU_RTCA_CHANNEL_INFO_LEN * id;
124 		if ((io_read32(membase) & ASU_CHNL_IPI_BITMASK) ==
125 		    (CFG_AMD_APU_LCL_IPI_ID << 16)) {
126 			channel_id = id;
127 			DMSG("Use ASU channel ID %"PRIu32, channel_id);
128 			break;
129 		}
130 	}
131 
132 	if (channel_id == ASU_MAX_IPI_CHANNELS)
133 		EMSG("Failed to identify ASU channel ID for APU");
134 
135 	ret = core_mmu_remove_mapping(MEM_AREA_IO_SEC,
136 				      comm_chnl_info, ASU_GLOBAL_ADDR_LIMIT);
137 	if (ret)
138 		EMSG("Failed to unmap RTCA");
139 
140 	return channel_id;
141 }
142 
143 /*
144  * asu_alloc_unique_id() - Generate a unique identifier for ASU operations
145  *
146  * Creates a unique ID by cycling through available IDs in the callback
147  * reference array. Ensures no ID collision by checking if the slot is
148  * already in use.
149  *
150  * Return: Unique ID (1 to ASU_UNIQUE_ID_MAX-1) or ASU_UNIQUE_ID_MAX if none
151  *	   available
152  */
asu_alloc_unique_id(void)153 uint8_t asu_alloc_unique_id(void)
154 {
155 	uint8_t unqid = 0;
156 	uint32_t state = 0;
157 
158 	state = cpu_spin_lock_xsave(&asuid.slock);
159 	while (unqid < ASU_UNIQUE_ID_MAX) {
160 		if (asuid.ids[unqid] == ASU_UNIQUE_ID_MAX) {
161 			asuid.ids[unqid] = unqid;
162 			DMSG("Got unique ID %"PRIu8, unqid);
163 			break;
164 		}
165 		unqid++;
166 	};
167 	cpu_spin_unlock_xrestore(&asuid.slock, state);
168 
169 	return unqid;
170 }
171 
172 /**
173  * asu_free_unique_id() - Release a previously allocated unique ID
174  * @uniqueid: The unique ID to be freed
175  *
176  * Marks the specified unique ID as available for reuse.
177  * The released ID is set to ASU_UNIQUE_ID_MAX
178  * to indicate its availability. Intended to be used in environments where
179  * concurrent access to the unique ID pool occurs.
180  */
asu_free_unique_id(uint8_t uniqueid)181 void asu_free_unique_id(uint8_t uniqueid)
182 {
183 	uint32_t state = 0;
184 
185 	state = cpu_spin_lock_xsave(&asuid.slock);
186 	asuid.ids[uniqueid] = ASU_UNIQUE_ID_MAX;
187 	cpu_spin_unlock_xrestore(&asuid.slock, state);
188 }
189 
190 /*
191  * get_free_index() - Find a free buffer index in the specified priority queue
192  * @priority: Priority level (ASU_PRIORITY_HIGH or ASU_PRIORITY_LOW)
193  *
194  * Searches for an available buffer slot in either the high priority (P0) or
195  * low priority (P1) channel queue. Updates the next free index pointer.
196  *
197  * Return: Buffer index (0 to ASU_MAX_BUFFERS-1) or ASU_MAX_BUFFERS if queue
198  *	   is full
199  */
get_free_index(uint8_t priority)200 static uint8_t get_free_index(uint8_t priority)
201 {
202 	struct asu_channel_queue *qptr = NULL;
203 	uint8_t index = 0;
204 	uint32_t state = 0;
205 
206 	state = cpu_spin_lock_xsave(&asu->slock);
207 
208 	if (priority == ASU_PRIORITY_HIGH)
209 		qptr = &asu->chnl_memptr->p0_chnl_q;
210 	else
211 		qptr = &asu->chnl_memptr->p1_chnl_q;
212 
213 	while (index < ASU_MAX_BUFFERS) {
214 		if (qptr->queue_bufs[index].reqbufstatus == 0U ||
215 		    qptr->queue_bufs[index].respbufstatus == 0U)
216 			break;
217 
218 		index++;
219 	}
220 
221 	if (index < ASU_MAX_BUFFERS)
222 		qptr->queue_bufs[index].reqbufstatus = ASU_COMMAND_IS_PRESENT;
223 
224 	cpu_spin_unlock_xrestore(&asu->slock, state);
225 
226 	return index;
227 }
228 
put_free_index(struct asu_channel_queue_buf * bufptr)229 static void put_free_index(struct asu_channel_queue_buf *bufptr)
230 {
231 	uint32_t state = 0;
232 
233 	state = cpu_spin_lock_xsave(&asu->slock);
234 	bufptr->reqbufstatus = 0;
235 	bufptr->respbufstatus = 0;
236 	cpu_spin_unlock_xrestore(&asu->slock, state);
237 }
238 
239 /*
240  * send_doorbell() - Send IPI doorbell interrupt to ASU
241  *
242  * Triggers an Inter-Processor Interrupt (IPI) to notify the ASU
243  * that a new command is available in the shared memory queue.
244  *
245  * Return: TEE_SUCCESS on successful doorbell trigger
246  */
send_doorbell(void)247 static TEE_Result send_doorbell(void)
248 {
249 	io_write32((vaddr_t)asu->doorbell + IPIPSU_TRIG_OFFSET,
250 		   ASU_TARGET_IPI_INT_MASK);
251 
252 	return TEE_SUCCESS;
253 }
254 
255 /*
256  * asu_update_queue_buffer_n_send_ipi() - Queue command and send IPI
257  * @param: Client parameters including priority
258  * @req_buffer: Request buffer containing command data
259  * @size: Size of request data
260  * @header: Command header information
261  * @status: FW return status
262  *
263  * Places a command in the appropriate priority queue buffer, updates
264  * queue status, and sends an IPI to notify ASU of the pending command.
265  *
266  * Return: TEE_SUCCESS on success, appropriate error code on failure
267  */
asu_update_queue_buffer_n_send_ipi(struct asu_client_params * param,void * req_buffer,uint32_t size,uint32_t header,uint32_t * status)268 TEE_Result asu_update_queue_buffer_n_send_ipi(struct asu_client_params *param,
269 					      void *req_buffer,
270 					      uint32_t size,
271 					      uint32_t header,
272 					      uint32_t *status)
273 {
274 	TEE_Result ret = TEE_ERROR_GENERIC;
275 	uint8_t freeindex = 0;
276 	struct asu_channel_queue_buf *bufptr = NULL;
277 	struct asu_channel_queue *qptr = NULL;
278 
279 	if (!param) {
280 		EMSG("Invalid parameters provided");
281 		return TEE_ERROR_BAD_PARAMETERS;
282 	}
283 
284 	if (asu->is_ready != ASU_CLIENT_READY) {
285 		EMSG("ASU client is not ready");
286 		return TEE_ERROR_BAD_STATE;
287 	}
288 
289 	freeindex = get_free_index(param->priority);
290 	if (freeindex == ASU_MAX_BUFFERS) {
291 		EMSG("ASU buffers full");
292 		return TEE_ERROR_SHORT_BUFFER;
293 	}
294 
295 	if (param->priority == ASU_PRIORITY_HIGH) {
296 		bufptr = &asu->chnl_memptr->p0_chnl_q.queue_bufs[freeindex];
297 		qptr = &asu->chnl_memptr->p0_chnl_q;
298 	} else {
299 		bufptr = &asu->chnl_memptr->p1_chnl_q.queue_bufs[freeindex];
300 		qptr = &asu->chnl_memptr->p1_chnl_q;
301 	}
302 
303 	bufptr->req.header = header;
304 	if (req_buffer && size != 0U)
305 		memcpy(bufptr->req.arg, req_buffer, size);
306 
307 	bufptr->respbufstatus = 0;
308 
309 	qptr->cmd_is_present = true;
310 	qptr->req_sent++;
311 	ret = send_doorbell();
312 	if (ret != TEE_SUCCESS) {
313 		EMSG("Failed to communicate to ASU");
314 		return TEE_ERROR_COMMUNICATION;
315 	}
316 	while (io_read8((vaddr_t)&bufptr->respbufstatus) !=
317 	       ASU_RESPONSE_IS_PRESENT) {
318 		/*
319 		 * WFE will return on SEV generated by the
320 		 * interrupt handler or by a spin_unlock
321 		 */
322 		wfe();
323 	}
324 
325 	*status = bufptr->resp.arg[ASU_RESPONSE_STATUS_INDEX];
326 	if (param->cbhandler && !*status)
327 		ret = param->cbhandler(param->cbptr, &bufptr->resp);
328 	put_free_index(bufptr);
329 
330 	return ret;
331 }
332 
asu_clear_intr(void)333 static void asu_clear_intr(void)
334 {
335 	uint32_t status = 0;
336 
337 	status = io_read32((vaddr_t)asu->doorbell + IPIPSU_ISR_OFFSET);
338 	io_write32((vaddr_t)asu->doorbell + IPIPSU_ISR_OFFSET,
339 		   status & IPIPSU_ALL_MASK);
340 }
341 
342 /*
343  * asu_resp_handler() - Interrupt handler for ASU responses
344  * @handler: Interrupt handler structure (unused)
345  *
346  * Interrupt service routine that processes responses from both high
347  * priority (P0) and low priority (P1) queues when ASU completes
348  * command processing.
349  *
350  * Return: ITRR_HANDLED indicating interrupt was handled
351  */
asu_resp_handler(struct itr_handler * handler __unused)352 static enum itr_return asu_resp_handler(struct itr_handler *handler __unused)
353 {
354 	sev();
355 	asu_clear_intr();
356 
357 	return ITRR_HANDLED;
358 }
359 
360 static struct itr_handler doorbell_handler = {
361 	.it = PAR_IPIPSU_0_INT_ID,
362 	.handler = asu_resp_handler,
363 };
364 
365 /*
366  * setup_doorbell() - Initialize doorbell interrupt handling
367  *
368  * Maps the doorbell register region, configures interrupt settings,
369  * registers the interrupt handler, and enables the interrupt for
370  * receiving ASU response notifications.
371  *
372  * Return: Pointer to mapped doorbell region or NULL on failure
373  */
setup_doorbell(void)374 static void *setup_doorbell(void)
375 {
376 	void *dbell = NULL;
377 	TEE_Result res = TEE_ERROR_GENERIC;
378 
379 	dbell = core_mmu_add_mapping(MEM_AREA_IO_SEC,
380 				     asu_configtable.baseaddr,
381 				     ASU_BASEADDR_SIZE);
382 	if (!dbell) {
383 		EMSG("Failed to map doorbell register");
384 		return dbell;
385 	}
386 
387 	io_write32((vaddr_t)dbell + IPIPSU_IER_OFFSET, IPIPSU_ALL_MASK);
388 	io_write32((vaddr_t)dbell + IPIPSU_ISR_OFFSET, IPIPSU_ALL_MASK);
389 
390 	doorbell_handler.chip = interrupt_get_main_chip();
391 
392 	res = interrupt_add_configure_handler(&doorbell_handler,
393 					      IRQ_TYPE_LEVEL_HIGH, 7);
394 	if (res)
395 		panic();
396 
397 	interrupt_enable(doorbell_handler.chip, doorbell_handler.it);
398 
399 	return dbell;
400 }
401 
asu_init_unique_id(void)402 static void asu_init_unique_id(void)
403 {
404 	uint32_t idx = 0;
405 
406 	asuid.slock = SPINLOCK_UNLOCK;
407 	for (idx = 0; idx < ARRAY_SIZE(asuid.ids); idx++)
408 		asuid.ids[idx] = ASU_UNIQUE_ID_MAX;
409 }
410 
411 /*
412  * asu_init() - Initialize the ASU driver and communication channel
413  *
414  * Performs complete ASU driver initialization including memory allocation,
415  * firmware readiness check, channel ID discovery, shared memory mapping,
416  * doorbell setup, and marking the client as ready for operation.
417  *
418  * Return: TEE_SUCCESS on successful initialization, appropriate error code on
419  *	   failure
420  */
asu_init(void)421 static TEE_Result asu_init(void)
422 {
423 	uint32_t channel_id = 0;
424 	void *asu_shmem = NULL;
425 	uint64_t membase = 0;
426 
427 	asu = calloc(1, sizeof(struct asu_client));
428 	if (!asu) {
429 		EMSG("Failed to allocate memory for ASU");
430 		return TEE_ERROR_OUT_OF_MEMORY;
431 	}
432 
433 	asu->global_ctrl = core_mmu_add_mapping(MEM_AREA_IO_SEC,
434 						ASU_BASEADDR,
435 						ASU_BASEADDR_SIZE);
436 	if (!asu->global_ctrl) {
437 		EMSG("Failed to initialize ASU");
438 		goto free;
439 	}
440 
441 	if (asu_fwcheck() != TEE_SUCCESS) {
442 		EMSG("ASU FW check failed");
443 		goto global_unmap;
444 	}
445 
446 	channel_id = asu_get_channelID();
447 
448 	if (channel_id == ASU_MAX_IPI_CHANNELS) {
449 		EMSG("ASU channel for APU not configured");
450 		goto global_unmap;
451 	}
452 
453 	membase = ASU_CHANNEL_MEMORY_BASEADDR +
454 			ASU_GLOBAL_ADDR_LIMIT * channel_id;
455 	asu_shmem = core_mmu_add_mapping(MEM_AREA_IO_SEC,
456 					 membase,
457 					 ASU_GLOBAL_ADDR_LIMIT);
458 	if (!asu_shmem) {
459 		EMSG("Failed to map ASU SHM");
460 		goto global_unmap;
461 	}
462 	asu_init_unique_id();
463 	asu->doorbell = setup_doorbell();
464 	if (!asu->doorbell) {
465 		EMSG("Failed to set up ASU doorbell");
466 		goto sh_unmap;
467 	}
468 
469 	asu->chnl_memptr = asu_shmem;
470 	asu->is_ready = ASU_CLIENT_READY;
471 	asu->slock = SPINLOCK_UNLOCK;
472 
473 	IMSG("ASU initialization complete");
474 
475 	return TEE_SUCCESS;
476 
477 sh_unmap:
478 	core_mmu_remove_mapping(MEM_AREA_IO_SEC, asu_shmem,
479 				ASU_GLOBAL_ADDR_LIMIT);
480 global_unmap:
481 	core_mmu_remove_mapping(MEM_AREA_IO_SEC, asu->global_ctrl,
482 				ASU_BASEADDR_SIZE);
483 free:
484 	free(asu);
485 
486 	EMSG("Failed to initialize ASU");
487 
488 	return TEE_ERROR_GENERIC;
489 }
490 
491 service_init(asu_init);
492