1 /*
2 * Copyright (c) 2014 Brian Swetland
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8
9 #include "lkboot.h"
10
11 #include <app.h>
12
13 #include <platform.h>
14 #include <stdio.h>
15 #include <lk/debug.h>
16 #include <string.h>
17 #include <lk/pow2.h>
18 #include <lk/err.h>
19 #include <assert.h>
20 #include <lk/trace.h>
21
22 #include <lib/sysparam.h>
23
24 #include <kernel/thread.h>
25 #include <kernel/mutex.h>
26
27 #include <kernel/vm.h>
28 #include <app/lkboot.h>
29
30 #if WITH_LIB_MINIP
31 #include <lib/minip.h>
32 #endif
33
34 #ifndef LKBOOT_WITH_SERVER
35 #define LKBOOT_WITH_SERVER 1
36 #endif
37 #ifndef LKBOOT_AUTOBOOT
38 #define LKBOOT_AUTOBOOT 1
39 #endif
40 #ifndef LKBOOT_AUTOBOOT_TIMEOUT
41 #define LKBOOT_AUTOBOOT_TIMEOUT 5000
42 #endif
43
44 #define LOCAL_TRACE 0
45
46 #define STATE_OPEN 0
47 #define STATE_DATA 1
48 #define STATE_RESP 2
49 #define STATE_DONE 3
50 #define STATE_ERROR 4
51
52 typedef struct LKB {
53 lkb_read_hook *read;
54 lkb_write_hook *write;
55
56 void *cookie;
57
58 int state;
59 size_t avail;
60 } lkb_t;
61
lkboot_create_lkb(void * cookie,lkb_read_hook * read,lkb_write_hook * write)62 lkb_t *lkboot_create_lkb(void *cookie, lkb_read_hook *read, lkb_write_hook *write) {
63 lkb_t *lkb = malloc(sizeof(lkb_t));
64 if (!lkb)
65 return NULL;
66
67 lkb->cookie = cookie;
68 lkb->state = STATE_OPEN;
69 lkb->avail = 0;
70 lkb->read = read;
71 lkb->write = write;
72
73 return lkb;
74 }
75
lkb_send(lkb_t * lkb,u8 opcode,const void * data,size_t len)76 static int lkb_send(lkb_t *lkb, u8 opcode, const void *data, size_t len) {
77 msg_hdr_t hdr;
78
79 // once we sent our OKAY or FAIL or errored out, no more writes
80 if (lkb->state >= STATE_DONE) return -1;
81
82 switch (opcode) {
83 case MSG_OKAY:
84 case MSG_FAIL:
85 lkb->state = STATE_DONE;
86 if (len > 0xFFFF) return -1;
87 break;
88 case MSG_LOG:
89 if (len > 0xFFFF) return -1;
90 break;
91 case MSG_SEND_DATA:
92 if (len > 0x10000) return -1;
93 break;
94 case MSG_GO_AHEAD:
95 if (lkb->state == STATE_OPEN) {
96 lkb->state = STATE_DATA;
97 break;
98 }
99 len = 0;
100 // fallthrough
101 default:
102 lkb->state = STATE_ERROR;
103 opcode = MSG_FAIL;
104 data = "internal error";
105 len = 14;
106 break;
107 }
108
109 hdr.opcode = opcode;
110 hdr.extra = 0;
111 hdr.length = (opcode == MSG_SEND_DATA) ? (len - 1) : len;
112 if (lkb->write(lkb->cookie, &hdr, sizeof(hdr)) != sizeof(&hdr)) {
113 printf("xmit hdr fail\n");
114 lkb->state = STATE_ERROR;
115 return -1;
116 }
117 if (len && (lkb->write(lkb->cookie, data, len) != (ssize_t)len)) {
118 printf("xmit data fail\n");
119 lkb->state = STATE_ERROR;
120 return -1;
121 }
122 return 0;
123 }
124
125 #define lkb_okay(lkb) lkb_send(lkb, MSG_OKAY, NULL, 0)
126 #define lkb_fail(lkb, msg) lkb_send(lkb, MSG_FAIL, msg, strlen(msg))
127
lkb_write(lkb_t * lkb,const void * _data,size_t len)128 int lkb_write(lkb_t *lkb, const void *_data, size_t len) {
129 const char *data = _data;
130 while (len > 0) {
131 size_t xfer = (len > 65536) ? 65536 : len;
132 if (lkb_send(lkb, MSG_SEND_DATA, data, xfer)) return -1;
133 len -= xfer;
134 data += xfer;
135 }
136 return 0;
137 }
138
lkb_read(lkb_t * lkb,void * _data,size_t len)139 int lkb_read(lkb_t *lkb, void *_data, size_t len) {
140 char *data = _data;
141
142 if (lkb->state == STATE_RESP) {
143 return 0;
144 }
145 if (lkb->state == STATE_OPEN) {
146 if (lkb_send(lkb, MSG_GO_AHEAD, NULL, 0)) return -1;
147 }
148 while (len > 0) {
149 if (lkb->avail == 0) {
150 msg_hdr_t hdr;
151 if (lkb->read(lkb->cookie, &hdr, sizeof(hdr))) goto fail;
152 if (hdr.opcode == MSG_END_DATA) {
153 lkb->state = STATE_RESP;
154 return -1;
155 }
156 if (hdr.opcode != MSG_SEND_DATA) goto fail;
157 lkb->avail = ((size_t) hdr.length) + 1;
158 }
159 if (lkb->avail >= len) {
160 if (lkb->read(lkb->cookie, data, len)) goto fail;
161 lkb->avail -= len;
162 return 0;
163 }
164 if (lkb->read(lkb->cookie, data, lkb->avail)) {
165 lkb->state = STATE_ERROR;
166 return -1;
167 }
168 data += lkb->avail;
169 len -= lkb->avail;
170 lkb->avail = 0;
171 }
172 return 0;
173
174 fail:
175 lkb->state = STATE_ERROR;
176 return -1;
177 }
178
lkboot_process_command(lkb_t * lkb)179 status_t lkboot_process_command(lkb_t *lkb) {
180 msg_hdr_t hdr;
181 char cmd[128];
182 char *arg;
183 int err;
184 const char *result;
185 unsigned len;
186
187 if (lkb->read(lkb->cookie, &hdr, sizeof(hdr))) goto fail;
188 if (hdr.opcode != MSG_CMD) goto fail;
189 if (hdr.length > 127) goto fail;
190 if (lkb->read(lkb->cookie, cmd, hdr.length)) goto fail;
191 cmd[hdr.length] = 0;
192
193 TRACEF("recv '%s'\n", cmd);
194
195 if (!(arg = strchr(cmd, ':'))) goto fail;
196 *arg++ = 0;
197 len = atoul(arg);
198 if (!(arg = strchr(arg, ':'))) goto fail;
199 arg++;
200
201 err = lkb_handle_command(lkb, cmd, arg, len, &result);
202 if (err >= 0) {
203 lkb_okay(lkb);
204 } else {
205 lkb_fail(lkb, result);
206 }
207
208 TRACEF("command handled with success\n");
209 return NO_ERROR;
210
211 fail:
212 TRACEF("command failed\n");
213 return ERR_IO;
214 }
215
lkboot_server(lk_time_t timeout)216 static status_t lkboot_server(lk_time_t timeout) {
217 lkboot_dcc_init();
218
219 #if WITH_LIB_MINIP
220 /* open the server's socket */
221 tcp_socket_t *listen_socket = NULL;
222 if (tcp_open_listen(&listen_socket, 1023) < 0) {
223 printf("lkboot: error opening listen socket\n");
224 return ERR_NO_MEMORY;
225 }
226 #endif
227
228 /* run the main lkserver loop */
229 printf("lkboot: starting server\n");
230 lk_time_t t = current_time(); /* remember when we started */
231 for (;;) {
232 bool handled_command = false;
233
234 lkb_t *lkb;
235
236 #if WITH_LIB_MINIP
237 /* wait for a new connection */
238 lk_time_t sock_timeout = 100;
239 tcp_socket_t *s;
240 if (tcp_accept_timeout(listen_socket, &s, sock_timeout) >= 0) {
241 DEBUG_ASSERT(s);
242
243 /* handle the command and close it */
244 lkb = lkboot_tcp_opened(s);
245 lkboot_process_command(lkb);
246 free(lkb);
247 tcp_close(s);
248 handled_command = true;
249 }
250 #endif
251
252 /* check if anything is coming in on dcc */
253 lkb = lkboot_check_dcc_open();
254 if (lkb) {
255 lkboot_process_command(lkb);
256 free(lkb);
257 handled_command = true;
258 }
259
260 /* after the first command, stay in the server loop forever */
261 if (handled_command && timeout != INFINITE_TIME) {
262 timeout = INFINITE_TIME;
263 printf("lkboot: handled command, staying in server loop\n");
264 }
265
266 /* see if we need to drop out and try to direct boot */
267 if (timeout != INFINITE_TIME && (current_time() - t >= timeout)) {
268 break;
269 }
270 }
271
272 #if WITH_LIB_MINIP
273 tcp_close(listen_socket);
274 #endif
275
276 printf("lkboot: server timed out\n");
277
278 return ERR_TIMED_OUT;
279 }
280
281 /* platform code can override this to conditionally abort autobooting from flash */
282 bool platform_abort_autoboot(void);
platform_abort_autoboot(void)283 __WEAK bool platform_abort_autoboot(void) {
284 return false;
285 }
286
lkboot_task(const struct app_descriptor * app,void * args)287 static void lkboot_task(const struct app_descriptor *app, void *args) {
288 /* read a few sysparams to decide if we're going to autoboot */
289 uint8_t autoboot = 1;
290 sysparam_read("lkboot.autoboot", &autoboot, sizeof(autoboot));
291
292 /* let platform code have a shot at disabling the autoboot behavior */
293 if (platform_abort_autoboot())
294 autoboot = 0;
295
296 #if !LKBOOT_AUTOBOOT
297 autoboot = 0;
298 #endif
299
300 /* if we're going to autoobot, read the timeout value */
301 lk_time_t autoboot_timeout;
302 if (!autoboot) {
303 autoboot_timeout = INFINITE_TIME;
304 } else {
305 autoboot_timeout = LKBOOT_AUTOBOOT_TIMEOUT;
306 sysparam_read("lkboot.autoboot_timeout", &autoboot_timeout, sizeof(autoboot_timeout));
307 }
308
309 TRACEF("autoboot %u autoboot_timeout %u\n", autoboot, (uint)autoboot_timeout);
310
311 #if LKBOOT_WITH_SERVER
312 lkboot_server(autoboot_timeout);
313 #else
314 if (autoboot_timeout != INFINITE_TIME) {
315 TRACEF("waiting for %u milliseconds before autobooting\n", (uint)autoboot_timeout);
316 thread_sleep(autoboot_timeout);
317 }
318 #endif
319
320 if (autoboot_timeout != INFINITE_TIME) {
321 TRACEF("trying to boot from flash...\n");
322 status_t err = do_flash_boot();
323 TRACEF("do_flash_boot returns %d\n", err);
324 }
325
326 #if LKBOOT_WITH_SERVER
327 TRACEF("restarting server\n");
328 lkboot_server(INFINITE_TIME);
329 #endif
330
331 TRACEF("nothing to do, exiting\n");
332 }
333
334 APP_START(lkboot)
335 .entry = lkboot_task,
336 .flags = 0,
337 APP_END
338