| /* |
| * Copyright (c) 2014 Brian Swetland |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files |
| * (the "Software"), to deal in the Software without restriction, |
| * including without limitation the rights to use, copy, modify, merge, |
| * publish, distribute, sublicense, and/or sell copies of the Software, |
| * and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| #include "lkboot.h" |
| |
| #include <app.h> |
| |
| #include <platform.h> |
| #include <stdio.h> |
| #include <debug.h> |
| #include <string.h> |
| #include <pow2.h> |
| #include <err.h> |
| #include <assert.h> |
| #include <trace.h> |
| |
| #include <lib/sysparam.h> |
| |
| #include <kernel/thread.h> |
| #include <kernel/mutex.h> |
| |
| #include <kernel/vm.h> |
| #include <app/lkboot.h> |
| |
| #if WITH_LIB_MINIP |
| #include <lib/minip.h> |
| #endif |
| |
| #ifndef LKBOOT_WITH_SERVER |
| #define LKBOOT_WITH_SERVER 1 |
| #endif |
| #ifndef LKBOOT_AUTOBOOT |
| #define LKBOOT_AUTOBOOT 1 |
| #endif |
| #ifndef LKBOOT_AUTOBOOT_TIMEOUT |
| #define LKBOOT_AUTOBOOT_TIMEOUT 5000 |
| #endif |
| |
| #define LOCAL_TRACE 0 |
| |
| #define STATE_OPEN 0 |
| #define STATE_DATA 1 |
| #define STATE_RESP 2 |
| #define STATE_DONE 3 |
| #define STATE_ERROR 4 |
| |
| typedef struct LKB { |
| lkb_read_hook *read; |
| lkb_write_hook *write; |
| |
| void *cookie; |
| |
| int state; |
| size_t avail; |
| } lkb_t; |
| |
| lkb_t *lkboot_create_lkb(void *cookie, lkb_read_hook *read, lkb_write_hook *write) { |
| lkb_t *lkb = malloc(sizeof(lkb_t)); |
| if (!lkb) |
| return NULL; |
| |
| lkb->cookie = cookie; |
| lkb->state = STATE_OPEN; |
| lkb->avail = 0; |
| lkb->read = read; |
| lkb->write = write; |
| |
| return lkb; |
| } |
| |
| static int lkb_send(lkb_t *lkb, u8 opcode, const void *data, size_t len) { |
| msg_hdr_t hdr; |
| |
| // once we sent our OKAY or FAIL or errored out, no more writes |
| if (lkb->state >= STATE_DONE) return -1; |
| |
| switch (opcode) { |
| case MSG_OKAY: |
| case MSG_FAIL: |
| lkb->state = STATE_DONE; |
| if (len > 0xFFFF) return -1; |
| break; |
| case MSG_LOG: |
| if (len > 0xFFFF) return -1; |
| break; |
| case MSG_SEND_DATA: |
| if (len > 0x10000) return -1; |
| break; |
| case MSG_GO_AHEAD: |
| if (lkb->state == STATE_OPEN) { |
| lkb->state = STATE_DATA; |
| break; |
| } |
| len = 0; |
| default: |
| lkb->state = STATE_ERROR; |
| opcode = MSG_FAIL; |
| data = "internal error"; |
| len = 14; |
| break; |
| } |
| |
| hdr.opcode = opcode; |
| hdr.extra = 0; |
| hdr.length = (opcode == MSG_SEND_DATA) ? (len - 1) : len; |
| if (lkb->write(lkb->cookie, &hdr, sizeof(hdr)) != sizeof(&hdr)) { |
| printf("xmit hdr fail\n"); |
| lkb->state = STATE_ERROR; |
| return -1; |
| } |
| if (len && (lkb->write(lkb->cookie, data, len) != (ssize_t)len)) { |
| printf("xmit data fail\n"); |
| lkb->state = STATE_ERROR; |
| return -1; |
| } |
| return 0; |
| } |
| |
| #define lkb_okay(lkb) lkb_send(lkb, MSG_OKAY, NULL, 0) |
| #define lkb_fail(lkb, msg) lkb_send(lkb, MSG_FAIL, msg, strlen(msg)) |
| |
| int lkb_write(lkb_t *lkb, const void *_data, size_t len) { |
| const char *data = _data; |
| while (len > 0) { |
| size_t xfer = (len > 65536) ? 65536 : len; |
| if (lkb_send(lkb, MSG_SEND_DATA, data, xfer)) return -1; |
| len -= xfer; |
| data += xfer; |
| } |
| return 0; |
| } |
| |
| int lkb_read(lkb_t *lkb, void *_data, size_t len) { |
| char *data = _data; |
| |
| if (lkb->state == STATE_RESP) { |
| return 0; |
| } |
| if (lkb->state == STATE_OPEN) { |
| if (lkb_send(lkb, MSG_GO_AHEAD, NULL, 0)) return -1; |
| } |
| while (len > 0) { |
| if (lkb->avail == 0) { |
| msg_hdr_t hdr; |
| if (lkb->read(lkb->cookie, &hdr, sizeof(hdr))) goto fail; |
| if (hdr.opcode == MSG_END_DATA) { |
| lkb->state = STATE_RESP; |
| return -1; |
| } |
| if (hdr.opcode != MSG_SEND_DATA) goto fail; |
| lkb->avail = ((size_t) hdr.length) + 1; |
| } |
| if (lkb->avail >= len) { |
| if (lkb->read(lkb->cookie, data, len)) goto fail; |
| lkb->avail -= len; |
| return 0; |
| } |
| if (lkb->read(lkb->cookie, data, lkb->avail)) { |
| lkb->state = STATE_ERROR; |
| return -1; |
| } |
| data += lkb->avail; |
| len -= lkb->avail; |
| lkb->avail = 0; |
| } |
| return 0; |
| |
| fail: |
| lkb->state = STATE_ERROR; |
| return -1; |
| } |
| |
| status_t lkboot_process_command(lkb_t *lkb) |
| { |
| msg_hdr_t hdr; |
| char cmd[128]; |
| char *arg; |
| int err; |
| const char *result; |
| unsigned len; |
| |
| if (lkb->read(lkb->cookie, &hdr, sizeof(hdr))) goto fail; |
| if (hdr.opcode != MSG_CMD) goto fail; |
| if (hdr.length > 127) goto fail; |
| if (lkb->read(lkb->cookie, cmd, hdr.length)) goto fail; |
| cmd[hdr.length] = 0; |
| |
| TRACEF("recv '%s'\n", cmd); |
| |
| if (!(arg = strchr(cmd, ':'))) goto fail; |
| *arg++ = 0; |
| len = atoul(arg); |
| if (!(arg = strchr(arg, ':'))) goto fail; |
| arg++; |
| |
| err = lkb_handle_command(lkb, cmd, arg, len, &result); |
| if (err >= 0) { |
| lkb_okay(lkb); |
| } else { |
| lkb_fail(lkb, result); |
| } |
| |
| TRACEF("command handled with success\n"); |
| return NO_ERROR; |
| |
| fail: |
| TRACEF("command failed\n"); |
| return ERR_IO; |
| } |
| |
| static status_t lkboot_server(lk_time_t timeout) |
| { |
| lkboot_dcc_init(); |
| |
| #if WITH_LIB_MINIP |
| /* open the server's socket */ |
| tcp_socket_t *listen_socket = NULL; |
| if (tcp_open_listen(&listen_socket, 1023) < 0) { |
| printf("lkboot: error opening listen socket\n"); |
| return ERR_NO_MEMORY; |
| } |
| #endif |
| |
| /* run the main lkserver loop */ |
| printf("lkboot: starting server\n"); |
| lk_time_t t = current_time(); /* remember when we started */ |
| for (;;) { |
| bool handled_command = false; |
| |
| lkb_t *lkb; |
| |
| #if WITH_LIB_MINIP |
| /* wait for a new connection */ |
| lk_time_t sock_timeout = 100; |
| tcp_socket_t *s; |
| if (tcp_accept_timeout(listen_socket, &s, sock_timeout) >= 0) { |
| DEBUG_ASSERT(s); |
| |
| /* handle the command and close it */ |
| lkb = lkboot_tcp_opened(s); |
| lkboot_process_command(lkb); |
| free(lkb); |
| tcp_close(s); |
| handled_command = true; |
| } |
| #endif |
| |
| /* check if anything is coming in on dcc */ |
| lkb = lkboot_check_dcc_open(); |
| if (lkb) { |
| lkboot_process_command(lkb); |
| free(lkb); |
| handled_command = true; |
| } |
| |
| /* after the first command, stay in the server loop forever */ |
| if (handled_command && timeout != INFINITE_TIME) { |
| timeout = INFINITE_TIME; |
| printf("lkboot: handled command, staying in server loop\n"); |
| } |
| |
| /* see if we need to drop out and try to direct boot */ |
| if (timeout != INFINITE_TIME && (current_time() - t >= timeout)) { |
| break; |
| } |
| } |
| |
| #if WITH_LIB_MINIP |
| tcp_close(listen_socket); |
| #endif |
| |
| printf("lkboot: server timed out\n"); |
| |
| return ERR_TIMED_OUT; |
| } |
| |
| /* platform code can override this to conditionally abort autobooting from flash */ |
| __WEAK bool platform_abort_autoboot(void) |
| { |
| return false; |
| } |
| |
| static void lkboot_task(const struct app_descriptor *app, void *args) |
| { |
| /* read a few sysparams to decide if we're going to autoboot */ |
| uint8_t autoboot = 1; |
| sysparam_read("lkboot.autoboot", &autoboot, sizeof(autoboot)); |
| |
| /* let platform code have a shot at disabling the autoboot behavior */ |
| if (platform_abort_autoboot()) |
| autoboot = 0; |
| |
| #if !LKBOOT_AUTOBOOT |
| autoboot = 0; |
| #endif |
| |
| /* if we're going to autoobot, read the timeout value */ |
| lk_time_t autoboot_timeout; |
| if (!autoboot) { |
| autoboot_timeout = INFINITE_TIME; |
| } else { |
| autoboot_timeout = LKBOOT_AUTOBOOT_TIMEOUT; |
| sysparam_read("lkboot.autoboot_timeout", &autoboot_timeout, sizeof(autoboot_timeout)); |
| } |
| |
| TRACEF("autoboot %u autoboot_timeout %u\n", autoboot, (uint)autoboot_timeout); |
| |
| #if LKBOOT_WITH_SERVER |
| lkboot_server(autoboot_timeout); |
| #else |
| if (autoboot_timeout != INFINITE_TIME) { |
| TRACEF("waiting for %u milliseconds before autobooting\n", (uint)autoboot_timeout); |
| thread_sleep(autoboot_timeout); |
| } |
| #endif |
| |
| if (autoboot_timeout != INFINITE_TIME) { |
| TRACEF("trying to boot from flash...\n"); |
| status_t err = do_flash_boot(); |
| TRACEF("do_flash_boot returns %d\n", err); |
| } |
| |
| #if LKBOOT_WITH_SERVER |
| TRACEF("restarting server\n"); |
| lkboot_server(INFINITE_TIME); |
| #endif |
| |
| TRACEF("nothing to do, exiting\n"); |
| } |
| |
| APP_START(lkboot) |
| .entry = lkboot_task, |
| .flags = 0, |
| APP_END |