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