1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved.
4  * Copyright (C) 2019-2022 Linaro Limited.
5  */
6 
7 #define LOG_CATEGORY UCLASS_SCMI_AGENT
8 
9 #include <cpu_func.h>
10 #include <dm.h>
11 #include <dm/device_compat.h>
12 #include <errno.h>
13 #include <scmi_agent.h>
14 #include <asm/cache.h>
15 #include <asm/system.h>
16 #include <dm/ofnode.h>
17 #include <linux/compat.h>
18 #include <linux/io.h>
19 #include <linux/ioport.h>
20 
21 #include "smt.h"
22 
scmi_smt_enable_intr(struct scmi_smt * smt,bool enable)23 static void scmi_smt_enable_intr(struct scmi_smt *smt, bool enable)
24 {
25 	struct scmi_smt_header *hdr = (void *)smt->buf;
26 
27 	if (enable)
28 		hdr->flags |= SCMI_SHMEM_FLAG_INTR_ENABLED;
29 	else
30 		hdr->flags &= ~SCMI_SHMEM_FLAG_INTR_ENABLED;
31 }
32 
33 /**
34  * Get shared memory configuration defined by the referred DT phandle
35  * Return with a errno compliant value.
36  */
scmi_dt_get_smt_buffer(struct udevice * dev,struct scmi_smt * smt)37 int scmi_dt_get_smt_buffer(struct udevice *dev, struct scmi_smt *smt)
38 {
39 	int ret;
40 	struct ofnode_phandle_args args;
41 	struct resource resource;
42 
43 	ret = dev_read_phandle_with_args(dev, "shmem", NULL, 0, 0, &args);
44 	if (ret)
45 		return ret;
46 
47 	ret = ofnode_read_resource(args.node, 0, &resource);
48 	if (ret)
49 		return ret;
50 
51 	smt->size = resource_size(&resource);
52 	if (smt->size < sizeof(struct scmi_smt_header)) {
53 		dev_err(dev, "Shared memory buffer too small\n");
54 		return -EINVAL;
55 	}
56 
57 	smt->buf = devm_ioremap(dev, resource.start, smt->size);
58 	if (!smt->buf)
59 		return -ENOMEM;
60 
61 	if (device_is_compatible(dev, "arm,scmi") && ofnode_has_property(dev_ofnode(dev), "mboxes"))
62 		scmi_smt_enable_intr(smt, true);
63 
64 #ifdef CONFIG_ARM
65 	if (dcache_status())
66 		mmu_set_region_dcache_behaviour(ALIGN_DOWN((uintptr_t)smt->buf, MMU_SECTION_SIZE),
67 						ALIGN(smt->size, MMU_SECTION_SIZE),
68 						DCACHE_OFF);
69 
70 #endif
71 
72 	return 0;
73 }
74 
75 /**
76  * Write SCMI message @msg into a SMT shared buffer @smt.
77  * Return 0 on success and with a negative errno in case of error.
78  */
scmi_write_msg_to_smt(struct udevice * dev,struct scmi_smt * smt,struct scmi_msg * msg)79 int scmi_write_msg_to_smt(struct udevice *dev, struct scmi_smt *smt,
80 			  struct scmi_msg *msg)
81 {
82 	struct scmi_smt_header *hdr = (void *)smt->buf;
83 
84 	if ((!msg->in_msg && msg->in_msg_sz) ||
85 	    (!msg->out_msg && msg->out_msg_sz))
86 		return -EINVAL;
87 
88 	if (!(hdr->channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE)) {
89 		dev_dbg(dev, "Channel busy\n");
90 		return -EBUSY;
91 	}
92 
93 	if (smt->size < (sizeof(*hdr) + msg->in_msg_sz) ||
94 	    smt->size < (sizeof(*hdr) + msg->out_msg_sz)) {
95 		dev_dbg(dev, "Buffer too small\n");
96 		return -ETOOSMALL;
97 	}
98 
99 	/* Load message in shared memory */
100 	hdr->channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE;
101 	hdr->length = msg->in_msg_sz + sizeof(hdr->msg_header);
102 	hdr->msg_header = SMT_HEADER_TOKEN(0) |
103 			  SMT_HEADER_MESSAGE_TYPE(0) |
104 			  SMT_HEADER_PROTOCOL_ID(msg->protocol_id) |
105 			  SMT_HEADER_MESSAGE_ID(msg->message_id);
106 
107 	memcpy_toio(hdr->msg_payload, msg->in_msg, msg->in_msg_sz);
108 
109 	return 0;
110 }
111 
112 /**
113  * Read SCMI message from a SMT shared buffer @smt and copy it into @msg.
114  * Return 0 on success and with a negative errno in case of error.
115  */
scmi_read_resp_from_smt(struct udevice * dev,struct scmi_smt * smt,struct scmi_msg * msg)116 int scmi_read_resp_from_smt(struct udevice *dev, struct scmi_smt *smt,
117 			    struct scmi_msg *msg)
118 {
119 	struct scmi_smt_header *hdr = (void *)smt->buf;
120 
121 	if (!(hdr->channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE)) {
122 		dev_err(dev, "Channel unexpectedly busy\n");
123 		return -EBUSY;
124 	}
125 
126 	if (hdr->channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR) {
127 		dev_err(dev, "Channel error reported, reset channel\n");
128 		return -ECOMM;
129 	}
130 
131 	if (hdr->length > msg->out_msg_sz + sizeof(hdr->msg_header)) {
132 		dev_err(dev, "Buffer to small\n");
133 		return -ETOOSMALL;
134 	}
135 
136 	/* Get the data */
137 	msg->out_msg_sz = hdr->length - sizeof(hdr->msg_header);
138 	memcpy_fromio(msg->out_msg, hdr->msg_payload, msg->out_msg_sz);
139 
140 	return 0;
141 }
142 
143 /**
144  * Clear SMT flags in shared buffer to allow further message exchange
145  */
scmi_clear_smt_channel(struct scmi_smt * smt)146 void scmi_clear_smt_channel(struct scmi_smt *smt)
147 {
148 	struct scmi_smt_header *hdr = (void *)smt->buf;
149 
150 	hdr->channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR;
151 }
152 
153 /**
154  * Write SCMI message @msg into a SMT_MSG shared buffer @smt.
155  * Return 0 on success and with a negative errno in case of error.
156  */
scmi_msg_to_smt_msg(struct udevice * dev,struct scmi_smt * smt,struct scmi_msg * msg,size_t * buf_size)157 int scmi_msg_to_smt_msg(struct udevice *dev, struct scmi_smt *smt,
158 			struct scmi_msg *msg, size_t *buf_size)
159 {
160 	struct scmi_smt_msg_header *hdr = (void *)smt->buf;
161 
162 	if ((!msg->in_msg && msg->in_msg_sz) ||
163 	    (!msg->out_msg && msg->out_msg_sz))
164 		return -EINVAL;
165 
166 	if (smt->size < (sizeof(*hdr) + msg->in_msg_sz) ||
167 	    smt->size < (sizeof(*hdr) + msg->out_msg_sz)) {
168 		dev_dbg(dev, "Buffer too small\n");
169 		return -ETOOSMALL;
170 	}
171 
172 	*buf_size = msg->in_msg_sz + sizeof(hdr->msg_header);
173 
174 	hdr->msg_header = SMT_HEADER_TOKEN(0) |
175 			  SMT_HEADER_MESSAGE_TYPE(0) |
176 			  SMT_HEADER_PROTOCOL_ID(msg->protocol_id) |
177 			  SMT_HEADER_MESSAGE_ID(msg->message_id);
178 
179 	memcpy(hdr->msg_payload, msg->in_msg, msg->in_msg_sz);
180 
181 	return 0;
182 }
183 
184 /**
185  * Read SCMI message from a SMT shared buffer @smt and copy it into @msg.
186  * Return 0 on success and with a negative errno in case of error.
187  */
scmi_msg_from_smt_msg(struct udevice * dev,struct scmi_smt * smt,struct scmi_msg * msg,size_t buf_size)188 int scmi_msg_from_smt_msg(struct udevice *dev, struct scmi_smt *smt,
189 			  struct scmi_msg *msg, size_t buf_size)
190 {
191 	struct scmi_smt_msg_header *hdr = (void *)smt->buf;
192 
193 	if (buf_size > msg->out_msg_sz + sizeof(hdr->msg_header)) {
194 		dev_err(dev, "Buffer to small\n");
195 		return -ETOOSMALL;
196 	}
197 
198 	msg->out_msg_sz = buf_size - sizeof(hdr->msg_header);
199 	memcpy(msg->out_msg, hdr->msg_payload, msg->out_msg_sz);
200 
201 	return 0;
202 }
203