• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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