smt.c 5.47 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
// SPDX-License-Identifier: BSD-3-Clause
/*
 * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved.
 * Copyright (c) 2019-2020, Linaro Limited
 */
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

Peng Fan's avatar
Peng Fan committed
11
12
#include <drivers/scmi-msg.h>
#include <drivers/scmi.h>
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
#include <lib/cassert.h>
#include <lib/mmio.h>
#include <lib/spinlock.h>
#include <lib/utils.h>
#include <plat/common/platform.h>

#include "common.h"

/* Legacy SMT/SCMI messages are 128 bytes at most including SMT header */
#define SCMI_PLAYLOAD_MAX		92U
#define SCMI_PLAYLOAD_U32_MAX		(SCMI_PLAYLOAD_MAX / sizeof(uint32_t))

/**
 * struct smt_header - SMT formatted header for SMT base shared memory transfer
 *
 * @status: Bit flags, see SMT_STATUS_*
 * @flags: Bit flags, see SMT_FLAG_*
 * @length: Byte size of message payload (variable) + ::message_header (32bit)
 * payload: SCMI message payload data
 */
struct smt_header {
	uint32_t reserved0;
	uint32_t status;
	uint64_t reserved1;
	uint32_t flags;
	uint32_t length; /* message_header + payload */
	uint32_t message_header;
	uint32_t payload[];
};

CASSERT(SCMI_PLAYLOAD_MAX + sizeof(struct smt_header) <= SMT_BUF_SLOT_SIZE,
	assert_scmi_message_max_length_fits_in_smt_buffer_slot);

/* Flag set in smt_header::status when SMT does not contain pending message */
#define SMT_STATUS_FREE			BIT(0)
/* Flag set in smt_header::status when SMT reports an error */
#define SMT_STATUS_ERROR		BIT(1)

/* Flag set in smt_header::flags when SMT uses interrupts */
#define SMT_FLAG_INTR_ENABLED		BIT(1)

/* Bit fields packed in smt_header::message_header */
#define SMT_MSG_ID_MASK			GENMASK_32(7, 0)
#define SMT_HDR_MSG_ID(_hdr)		((_hdr) & SMT_MSG_ID_MASK)

#define SMT_MSG_TYPE_MASK		GENMASK_32(9, 8)
#define SMT_HDR_TYPE_ID(_hdr)		(((_hdr) & SMT_MSG_TYPE_MASK) >> 8)

#define SMT_MSG_PROT_ID_MASK		GENMASK_32(17, 10)
#define SMT_HDR_PROT_ID(_hdr)		(((_hdr) & SMT_MSG_PROT_ID_MASK) >> 10)

/*
 * Provision input message payload buffers for fastcall SMC context entries
 * and for interrupt context execution entries.
 */
static uint32_t fast_smc_payload[PLATFORM_CORE_COUNT][SCMI_PLAYLOAD_U32_MAX];
static uint32_t interrupt_payload[PLATFORM_CORE_COUNT][SCMI_PLAYLOAD_U32_MAX];

/* SMP protection on channel access */
static struct spinlock smt_channels_lock;

/* If channel is not busy, set busy and return true, otherwise return false */
static bool channel_set_busy(struct scmi_msg_channel *chan)
{
	bool channel_is_busy;

	spin_lock(&smt_channels_lock);

	channel_is_busy = chan->busy;

	if (!channel_is_busy) {
		chan->busy = true;
	}

	spin_unlock(&smt_channels_lock);

	return !channel_is_busy;
}

static void channel_release_busy(struct scmi_msg_channel *chan)
{
	chan->busy = false;
}

static struct smt_header *channel_to_smt_hdr(struct scmi_msg_channel *chan)
{
	return (struct smt_header *)chan->shm_addr;
}

/*
 * Creates a SCMI message instance in secure memory and pushes it in the SCMI
 * message drivers. Message structure contains SCMI protocol meta-data and
 * references to input payload in secure memory and output message buffer
 * in shared memory.
 */
static void scmi_proccess_smt(unsigned int agent_id, uint32_t *payload_buf)
{
	struct scmi_msg_channel *chan;
	struct smt_header *smt_hdr;
	size_t in_payload_size;
	uint32_t smt_status;
	struct scmi_msg msg;
	bool error = true;

	chan = plat_scmi_get_channel(agent_id);
	if (chan == NULL) {
		return;
	}

	smt_hdr = channel_to_smt_hdr(chan);
	assert(smt_hdr);

	smt_status = __atomic_load_n(&smt_hdr->status, __ATOMIC_RELAXED);

	if (!channel_set_busy(chan)) {
		VERBOSE("SCMI channel %u busy", agent_id);
		goto out;
	}

	in_payload_size = __atomic_load_n(&smt_hdr->length, __ATOMIC_RELAXED) -
			  sizeof(smt_hdr->message_header);

	if (in_payload_size > SCMI_PLAYLOAD_MAX) {
		VERBOSE("SCMI payload too big %u", in_payload_size);
		goto out;
	}

	if ((smt_status & (SMT_STATUS_ERROR | SMT_STATUS_FREE)) != 0U) {
		VERBOSE("SCMI channel bad status 0x%x",
			smt_hdr->status & (SMT_STATUS_ERROR | SMT_STATUS_FREE));
		goto out;
	}

	/* Fill message */
	zeromem(&msg, sizeof(msg));
	msg.in = (char *)payload_buf;
	msg.in_size = in_payload_size;
	msg.out = (char *)smt_hdr->payload;
	msg.out_size = chan->shm_size - sizeof(*smt_hdr);

	assert((msg.out != NULL) && (msg.out_size >= sizeof(int32_t)));

	/* Here the payload is copied in secure memory */
	memcpy(msg.in, smt_hdr->payload, in_payload_size);

	msg.protocol_id = SMT_HDR_PROT_ID(smt_hdr->message_header);
	msg.message_id = SMT_HDR_MSG_ID(smt_hdr->message_header);
	msg.agent_id = agent_id;

	scmi_process_message(&msg);

	/* Update message length with the length of the response message */
	smt_hdr->length = msg.out_size_out + sizeof(smt_hdr->message_header);

	channel_release_busy(chan);
	error = false;

out:
	if (error) {
		VERBOSE("SCMI error");
		smt_hdr->status |= SMT_STATUS_ERROR | SMT_STATUS_FREE;
	} else {
		smt_hdr->status |= SMT_STATUS_FREE;
	}
}

void scmi_smt_fastcall_smc_entry(unsigned int agent_id)
{
	scmi_proccess_smt(agent_id,
			  fast_smc_payload[plat_my_core_pos()]);
}

void scmi_smt_interrupt_entry(unsigned int agent_id)
{
	scmi_proccess_smt(agent_id,
			  interrupt_payload[plat_my_core_pos()]);
}

/* Init a SMT header for a shared memory buffer: state it a free/no-error */
void scmi_smt_init_agent_channel(struct scmi_msg_channel *chan)
{
	if (chan != NULL) {
		struct smt_header *smt_header = channel_to_smt_hdr(chan);

		if (smt_header != NULL) {
			memset(smt_header, 0, sizeof(*smt_header));
			smt_header->status = SMT_STATUS_FREE;

			return;
		}
	}

	panic();
}