1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <nos/transport.h>
18
19 #include <errno.h>
20 #include <stdarg.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include <application.h>
27
28 /* Note: evaluates expressions multiple times */
29 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
30
31 #define VERBOSE_LOG 0
32 #define DEBUG_LOG 0
33
34 #ifdef ANDROID
35 /* Logging for Android */
36 #define LOG_TAG "libnos_transport"
37 #include <sys/types.h>
38 #include <log/log.h>
39
40 #define NLOGE(...) ALOGE(__VA_ARGS__)
41 #define NLOGV(...) ALOGV(__VA_ARGS__)
42 #define NLOGD(...) ALOGD(__VA_ARGS__)
43
44 extern int usleep (uint32_t usec);
45
46 #else
47 /* Logging for other platforms */
48 #include <stdio.h>
49
50 #define NLOGE(...) do { fprintf(stderr, __VA_ARGS__); \
51 fprintf(stderr, "\n"); } while (0)
52 #define NLOGV(...) do { if (VERBOSE_LOG) { \
53 printf(__VA_ARGS__); printf("\n"); } } while (0)
54 #define NLOGD(...) do { if (DEBUG_LOG) { \
55 printf(__VA_ARGS__); printf("\n"); } } while (0)
56
57 #endif
58
59 /*
60 * If Citadel is rebooting it will take a while to become responsive again. We
61 * expect a reboot to take around 100ms but we'll keep trying for 300ms to leave
62 * plenty of margin.
63 */
64 #define RETRY_COUNT 60
65 #define RETRY_WAIT_TIME_US 5000
66
nos_device_read(const struct nos_device * dev,uint32_t command,uint8_t * buf,uint32_t len)67 static int nos_device_read(const struct nos_device *dev, uint32_t command,
68 uint8_t *buf, uint32_t len) {
69 int retries = RETRY_COUNT;
70 while (retries--) {
71 int err = dev->ops.read(dev->ctx, command, buf, len);
72
73 if (err == -EAGAIN) {
74 /* Linux driver returns EAGAIN error if Citadel chip is asleep.
75 * Give to the chip a little bit of time to awake and retry reading
76 * status again. */
77 usleep(RETRY_WAIT_TIME_US);
78 continue;
79 }
80
81 if (err) {
82 NLOGE("Failed to read: %s", strerror(-err));
83 }
84 return -err;
85 }
86
87 return ETIMEDOUT;
88 }
89
nos_device_write(const struct nos_device * dev,uint32_t command,uint8_t * buf,uint32_t len)90 static int nos_device_write(const struct nos_device *dev, uint32_t command,
91 uint8_t *buf, uint32_t len) {
92 int retries = RETRY_COUNT;
93 while (retries--) {
94 int err = dev->ops.write(dev->ctx, command, buf, len);
95
96 if (err == -EAGAIN) {
97 /* Linux driver returns EAGAIN error if Citadel chip is asleep.
98 * Give to the chip a little bit of time to awake and retry reading
99 * status again. */
100 usleep(RETRY_WAIT_TIME_US);
101 continue;
102 }
103
104 if (err) {
105 NLOGE("Failed to write: %s", strerror(-err));
106 }
107 return -err;
108 }
109
110 return ETIMEDOUT;
111 }
112
113 /* status is non-zero on error */
get_status(const struct nos_device * dev,uint8_t app_id,uint32_t * status,uint16_t * ulen)114 static int get_status(const struct nos_device *dev,
115 uint8_t app_id, uint32_t *status, uint16_t *ulen)
116 {
117 uint8_t buf[6];
118 uint32_t command = CMD_ID(app_id) | CMD_IS_READ | CMD_TRANSPORT;
119
120 if (0 != nos_device_read(dev, command, buf, sizeof(buf))) {
121 NLOGE("Failed to read device status");
122 return -1;
123 }
124
125 /* The read operation is successful */
126 *status = *(uint32_t *)buf;
127 *ulen = *(uint16_t *)(buf + 4);
128 return 0;
129 }
130
clear_status(const struct nos_device * dev,uint8_t app_id)131 static int clear_status(const struct nos_device *dev, uint8_t app_id)
132 {
133 uint32_t command = CMD_ID(app_id) | CMD_TRANSPORT;
134
135 if (0 != nos_device_write(dev, command, 0, 0)) {
136 NLOGE("Failed to clear device status");
137 return -1;
138 }
139
140 return 0;
141 }
142
nos_call_application(const struct nos_device * dev,uint8_t app_id,uint16_t params,const uint8_t * args,uint32_t arg_len,uint8_t * reply,uint32_t * reply_len)143 uint32_t nos_call_application(const struct nos_device *dev,
144 uint8_t app_id, uint16_t params,
145 const uint8_t *args, uint32_t arg_len,
146 uint8_t *reply, uint32_t *reply_len)
147 {
148 uint32_t command;
149 uint8_t buf[MAX_DEVICE_TRANSFER];
150 uint32_t status;
151 uint16_t ulen;
152 uint32_t poll_count = 0;
153
154 if (get_status(dev, app_id, &status, &ulen) != 0) {
155 return APP_ERROR_IO;
156 }
157 NLOGV("%d: query status 0x%08x ulen 0x%04x", __LINE__, status, ulen);
158
159 /* It's not idle, but we're the only ones telling it what to do, so it
160 * should be. */
161 if (status != APP_STATUS_IDLE) {
162
163 /* Try clearing the status */
164 NLOGV("clearing previous status");
165 if (clear_status(dev, app_id) != 0) {
166 return APP_ERROR_IO;
167 }
168
169 /* Check again */
170 if (get_status(dev, app_id, &status, &ulen) != 0) {
171 return APP_ERROR_IO;
172 }
173 NLOGV("%d: query status 0x%08x ulen 0x%04x",__LINE__, status, ulen);
174
175 /* It's ignoring us and is still not ready, so it's broken */
176 if (status != APP_STATUS_IDLE) {
177 NLOGE("Device is not responding");
178 return APP_ERROR_IO;
179 }
180 }
181
182 /* Send args data */
183 command = CMD_ID(app_id) | CMD_TRANSPORT | CMD_IS_DATA;
184 do {
185 /*
186 * We can't send more than the device can take. For
187 * Citadel using the TPM Wait protocol on SPS, this is
188 * a constant. For other buses, it may not be.
189 *
190 * For each Write, Citadel requires that we send the length of
191 * what we're about to send in the params field.
192 */
193 ulen = MIN(arg_len, MAX_DEVICE_TRANSFER);
194 CMD_SET_PARAM(command, ulen);
195 if (args && ulen)
196 memcpy(buf, args, ulen);
197
198 NLOGV("Write command 0x%08x, bytes 0x%x", command, ulen);
199
200 if (0 != nos_device_write(dev, command, buf, ulen)) {
201 NLOGE("Failed to send datagram to device");
202 return APP_ERROR_IO;
203 }
204
205 /* Additional data needs the additional flag set */
206 command |= CMD_MORE_TO_COME;
207
208 if (args)
209 args += ulen;
210 if (arg_len)
211 arg_len -= ulen;
212 } while (arg_len);
213
214 /* See if we had any errors while sending the args */
215 if (get_status(dev, app_id, &status, &ulen) != 0) {
216 return APP_ERROR_IO;
217 }
218 NLOGV("%d: query status 0x%08x ulen 0x%04x", __LINE__, status, ulen);
219 if (status & APP_STATUS_DONE)
220 /* Yep, problems. It should still be idle. */
221 goto reply;
222
223 /* Now tell the app to do whatever */
224 command = CMD_ID(app_id) | CMD_PARAM(params);
225 NLOGV("Write command 0x%08x", command);
226 if (0 != nos_device_write(dev, command, 0, 0)) {
227 NLOGE("Failed to send command datagram to device");
228 return APP_ERROR_IO;
229 }
230
231 /* Poll the app status until it's done */
232 do {
233 if (get_status(dev, app_id, &status, &ulen) != 0) {
234 return APP_ERROR_IO;
235 }
236 NLOGD("%d: poll status 0x%08x ulen 0x%04x", __LINE__, status, ulen);
237 poll_count++;
238 } while (!(status & APP_STATUS_DONE));
239 NLOGV("polled %d times, status 0x%08x ulen 0x%04x", poll_count,
240 status, ulen);
241
242 reply:
243 /* Read any result only if there's a place with room to put it */
244 if (reply && reply_len && *reply_len) {
245 uint16_t left = MIN(*reply_len, ulen);
246 uint16_t gimme, got;
247
248 command = CMD_ID(app_id) | CMD_IS_READ |
249 CMD_TRANSPORT | CMD_IS_DATA;
250
251 got = 0 ;
252 while (left) {
253
254 /*
255 * We can't read more than the device can send. For
256 * Citadel using the TPM Wait protocol on SPS, this is
257 * a constant. For other buses, it may not be.
258 */
259 gimme = MIN(left, MAX_DEVICE_TRANSFER);
260 NLOGV("Read command 0x%08x, bytes 0x%x", command, gimme);
261 if (0 != nos_device_read(dev, command, buf, gimme)) {
262 NLOGE("Failed to receive datagram from device");
263 return APP_ERROR_IO;
264 }
265
266 memcpy(reply, buf, gimme);
267 reply += gimme;
268 left -= gimme;
269 got += gimme;
270 }
271 /* got it all */
272 *reply_len = got;
273 }
274
275 /* Clear the reply manually for the next caller */
276 command = CMD_ID(app_id) | CMD_TRANSPORT;
277 if (0 != nos_device_write(dev, command, 0, 0)) {
278 NLOGE("Failed to clear the reply");
279 return APP_ERROR_IO;
280 }
281
282 return APP_STATUS_CODE(status);
283 }
284