[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/app/lkboot/commands.c b/src/bsp/lk/app/lkboot/commands.c
new file mode 100644
index 0000000..c6c45f3
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/commands.c
@@ -0,0 +1,413 @@
+/*
+ * 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 <platform.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <debug.h>
+#include <string.h>
+#include <endian.h>
+#include <malloc.h>
+#include <arch.h>
+#include <err.h>
+#include <trace.h>
+#include <pow2.h>
+
+#include <kernel/thread.h>
+#include <kernel/vm.h>
+
+#include <lib/bio.h>
+#include <lib/bootargs.h>
+#include <lib/bootimage.h>
+#include <lib/ptable.h>
+#include <lib/sysparam.h>
+
+#include <app/lkboot.h>
+
+#if PLATFORM_ZYNQ
+#include <platform/fpga.h>
+#include <platform/zynq.h>
+#endif
+
+#define bootdevice "spi0"
+
+#define LOCAL_TRACE 0
+
+struct lkb_command {
+    struct lkb_command *next;
+    const char *name;
+    lkb_handler_t handler;
+    void *cookie;
+};
+
+struct lkb_command *lkb_cmd_list = NULL;
+
+void lkb_register(const char *name, lkb_handler_t handler, void *cookie) {
+    struct lkb_command *cmd = malloc(sizeof(struct lkb_command));
+    if (cmd != NULL) {
+        cmd->next = lkb_cmd_list;
+        cmd->name = name;
+        cmd->handler = handler;
+        cmd->cookie = cookie;
+        lkb_cmd_list = cmd;
+    }
+}
+
+static int do_reboot(void *arg) {
+    thread_sleep(250);
+    platform_halt(HALT_ACTION_REBOOT, HALT_REASON_SW_RESET);
+    return 0;
+}
+
+struct chainload_args {
+    void *func;
+    ulong args[4];
+};
+
+static int chainload_thread(void *arg)
+{
+    struct chainload_args *args = (struct chainload_args *)arg;
+
+    thread_sleep(250);
+
+    TRACEF("chain loading address %p, args 0x%lx 0x%lx 0x%lx 0x%lx\n",
+            args->func, args->args[0], args->args[1], args->args[2], args->args[3]);
+    arch_chain_load((void *)args->func, args->args[0], args->args[1], args->args[2], args->args[3]);
+
+    for (;;);
+}
+
+static int do_boot(lkb_t *lkb, size_t len, const char **result)
+{
+    LTRACEF("lkb %p, len %zu, result %p\n", lkb, len, result);
+
+    void *buf;
+    paddr_t buf_phys;
+
+    if (vmm_alloc_contiguous(vmm_get_kernel_aspace(), "lkboot_iobuf",
+        len, &buf, log2_uint(1024*1024), 0, ARCH_MMU_FLAG_UNCACHED) < 0) {
+        *result = "not enough memory";
+        return -1;
+    }
+    arch_mmu_query((vaddr_t)buf, &buf_phys, NULL);
+    LTRACEF("iobuffer %p (phys 0x%lx)\n", buf, buf_phys);
+
+    if (lkb_read(lkb, buf, len)) {
+        *result = "io error";
+        // XXX free buffer here
+        return -1;
+    }
+
+    /* construct a boot argument list */
+    const size_t bootargs_size = PAGE_SIZE;
+#if 0
+    void *args = (void *)((uintptr_t)lkb_iobuffer + lkb_iobuffer_size - bootargs_size);
+    paddr_t args_phys = lkb_iobuffer_phys + lkb_iobuffer_size - bootargs_size;
+#elif PLATFORM_ZYNQ
+    /* grab the top page of sram */
+    /* XXX do this better */
+    paddr_t args_phys = SRAM_BASE + SRAM_SIZE - bootargs_size;
+    void *args = paddr_to_kvaddr(args_phys);
+#else
+#error need better way
+#endif
+    LTRACEF("boot args %p, phys 0x%lx, len %zu\n", args, args_phys, bootargs_size);
+
+    bootargs_start(args, bootargs_size);
+    bootargs_add_command_line(args, bootargs_size, "what what");
+    arch_clean_cache_range((vaddr_t)args, bootargs_size);
+
+    ulong lk_args[4];
+    bootargs_generate_lk_arg_values(args_phys, lk_args);
+
+    const void *ptr;
+
+    /* sniff it to see if it's a bootimage or a raw image */
+    bootimage_t *bi;
+    if (bootimage_open(buf, len, &bi) >= 0) {
+        size_t len;
+
+        /* it's a bootimage */
+        TRACEF("detected bootimage\n");
+
+        /* find the lk image */
+        if (bootimage_get_file_section(bi, TYPE_LK, &ptr, &len) >= 0) {
+            TRACEF("found lk section at %p\n", ptr);
+
+            /* add the boot image to the argument list */
+            size_t bootimage_size;
+            bootimage_get_range(bi, NULL, &bootimage_size);
+
+            bootargs_add_bootimage_pointer(args, bootargs_size, "pmem", buf_phys, bootimage_size);
+        }
+    } else {
+        /* raw image, just chain load it directly */
+        TRACEF("raw image, chainloading\n");
+
+        ptr = buf;
+    }
+
+    /* start a boot thread to complete the startup */
+    static struct chainload_args cl_args;
+
+    cl_args.func = (void *)ptr;
+    cl_args.args[0] = lk_args[0];
+    cl_args.args[1] = lk_args[1];
+    cl_args.args[2] = lk_args[2];
+    cl_args.args[3] = lk_args[3];
+
+    thread_resume(thread_create("boot", &chainload_thread, &cl_args,
+        DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+
+    return 0;
+}
+
+/* try to boot the system from a flash partition */
+status_t do_flash_boot(void)
+{
+    status_t err;
+
+    LTRACE_ENTRY;
+
+    /* construct a boot argument list */
+    const size_t bootargs_size = PAGE_SIZE;
+#if 0
+    /* old code */
+    void *args = (void *)((uintptr_t)lkb_iobuffer + lkb_iobuffer_size - bootargs_size);
+    paddr_t args_phys = lkb_iobuffer_phys + lkb_iobuffer_size - bootargs_size;
+#elif PLATFORM_ZYNQ
+    /* grab the top page of sram */
+    paddr_t args_phys = SRAM_BASE + SRAM_SIZE - bootargs_size;
+    void *args = paddr_to_kvaddr(args_phys);
+#else
+#error need better way
+#endif
+    LTRACEF("boot args %p, phys 0x%lx, len %zu\n", args, args_phys, bootargs_size);
+
+    bootargs_start(args, bootargs_size);
+    bootargs_add_command_line(args, bootargs_size, "what what");
+    arch_clean_cache_range((vaddr_t)args, bootargs_size);
+
+    ulong lk_args[4];
+    bootargs_generate_lk_arg_values(args_phys, lk_args);
+
+    const void *ptr;
+
+    if (!ptable_found_valid()) {
+        TRACEF("ptable not found\n");
+        return ERR_NOT_FOUND;
+    }
+
+    /* find the system partition */
+    struct ptable_entry entry;
+    err = ptable_find("system", &entry);
+    if (err < 0) {
+        TRACEF("cannot find system partition\n");
+        return ERR_NOT_FOUND;
+    }
+
+    /* get a direct pointer to the device */
+    bdev_t *bdev = ptable_get_device();
+    if (!bdev) {
+        TRACEF("error opening boot device\n");
+        return ERR_NOT_FOUND;
+    }
+
+    /* convert the bdev to a memory pointer */
+    err = bio_ioctl(bdev, BIO_IOCTL_GET_MEM_MAP, (void *)&ptr);
+    TRACEF("err %d, ptr %p\n", err, ptr);
+    if (err < 0) {
+        TRACEF("error getting direct pointer to block device\n");
+        return ERR_NOT_FOUND;
+    }
+
+    /* sniff it to see if it's a bootimage or a raw image */
+    bootimage_t *bi;
+    if (bootimage_open((char *)ptr + entry.offset, entry.length, &bi) >= 0) {
+        size_t len;
+
+        /* it's a bootimage */
+        TRACEF("detected bootimage\n");
+
+        /* find the lk image */
+        if (bootimage_get_file_section(bi, TYPE_LK, &ptr, &len) >= 0) {
+            TRACEF("found lk section at %p\n", ptr);
+
+            /* add the boot image to the argument list */
+            size_t bootimage_size;
+            bootimage_get_range(bi, NULL, &bootimage_size);
+
+            bootargs_add_bootimage_pointer(args, bootargs_size, bdev->name, entry.offset, bootimage_size);
+        }
+    } else {
+        /* did not find a bootimage, abort */
+        bio_ioctl(bdev, BIO_IOCTL_PUT_MEM_MAP, NULL);
+        return ERR_NOT_FOUND;
+    }
+
+    TRACEF("chain loading binary at %p\n", ptr);
+    arch_chain_load((void *)ptr, lk_args[0], lk_args[1], lk_args[2], lk_args[3]);
+
+    /* put the block device back into block mode (though we never get here) */
+    bio_ioctl(bdev, BIO_IOCTL_PUT_MEM_MAP, NULL);
+
+    return NO_ERROR;
+}
+
+// return NULL for success, error string for failure
+int lkb_handle_command(lkb_t *lkb, const char *cmd, const char *arg, size_t len, const char **result)
+{
+    *result = NULL;
+
+    struct lkb_command *lcmd;
+    for (lcmd = lkb_cmd_list; lcmd; lcmd = lcmd->next) {
+        if (!strcmp(lcmd->name, cmd)) {
+            *result = lcmd->handler(lkb, arg, len, lcmd->cookie);
+            return 0;
+        }
+    }
+
+    if (!strcmp(cmd, "flash") || !strcmp(cmd, "erase")) {
+        struct ptable_entry entry;
+        bdev_t *bdev;
+
+        if (ptable_find(arg, &entry) < 0) {
+            size_t plen = len;
+            /* doesn't exist, make one */
+            if (ptable_add(arg, plen, 0) < 0) {
+                *result = "error creating partition";
+                return -1;
+            }
+
+            if (ptable_find(arg, &entry) < 0) {
+                *result = "couldn't find partition after creating it";
+                return -1;
+            }
+        }
+        if (len > entry.length) {
+            *result = "partition too small";
+            return -1;
+        }
+
+        if (!(bdev = ptable_get_device())) {
+            *result = "ptable_get_device failed";
+            return -1;
+        }
+
+        printf("lkboot: erasing partition of size %llu\n", entry.length);
+        if (bio_erase(bdev, entry.offset, entry.length) != (ssize_t)entry.length) {
+            *result = "bio_erase failed";
+            return -1;
+        }
+
+        if (!strcmp(cmd, "flash")) {
+            printf("lkboot: writing to partition\n");
+
+            void *buf = malloc(bdev->block_size);
+            if (!buf) {
+                *result = "memory allocation failed";
+                return -1;
+            }
+
+            size_t pos = 0;
+            while (pos < len) {
+                size_t toread = MIN(len - pos, bdev->block_size);
+
+                LTRACEF("offset %zu, toread %zu\n", pos, toread);
+
+                if (lkb_read(lkb, buf, toread)) {
+                    *result = "io error";
+                    free(buf);
+                    return -1;
+                }
+
+                if (bio_write(bdev, buf, entry.offset + pos, toread) != (ssize_t)toread) {
+                    *result = "bio_write failed";
+                    free(buf);
+                    return -1;
+                }
+
+                pos += toread;
+            }
+
+            free(buf);
+        }
+    } else if (!strcmp(cmd, "remove")) {
+        if (ptable_remove(arg) < 0) {
+            *result = "remove failed";
+            return -1;
+        }
+    } else if (!strcmp(cmd, "fpga")) {
+#if PLATFORM_ZYNQ
+        void *buf = malloc(len);
+        if (!buf) {
+            *result = "error allocating buffer";
+            return -1;
+        }
+
+        /* translate to physical address */
+        paddr_t pa = kvaddr_to_paddr(buf);
+        if (pa == 0) {
+            *result = "error allocating buffer";
+            free(buf);
+            return -1;
+
+        }
+
+        if (lkb_read(lkb, buf, len)) {
+            *result = "io error";
+            free(buf);
+            return -1;
+        }
+
+        /* make sure the cache is flushed for this buffer for DMA coherency purposes */
+        arch_clean_cache_range((vaddr_t)buf, len);
+
+        /* program the fpga */
+        zynq_reset_fpga();
+        zynq_program_fpga(pa, len);
+
+        free(buf);
+#else
+        *result = "no fpga";
+        return -1;
+#endif
+    } else if (!strcmp(cmd, "boot")) {
+        return do_boot(lkb, len, result);
+    } else if (!strcmp(cmd, "getsysparam")) {
+        const void *ptr;
+        size_t len;
+        if (sysparam_get_ptr(arg, &ptr, &len) == 0) {
+            lkb_write(lkb, ptr, len);
+        }
+    } else if (!strcmp(cmd, "reboot")) {
+        thread_resume(thread_create("reboot", &do_reboot, NULL,
+            DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+    } else {
+        *result = "unknown command";
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/src/bsp/lk/app/lkboot/dcc.c b/src/bsp/lk/app/lkboot/dcc.c
new file mode 100644
index 0000000..e12b99b
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/dcc.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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 <stdio.h>
+#include <debug.h>
+#include <string.h>
+#include <compiler.h>
+#include <err.h>
+#include <assert.h>
+#include <trace.h>
+#include <stdlib.h>
+#include <lib/cbuf.h>
+#include <app/lkboot.h>
+#include <arch/arm/dcc.h>
+#include <arch/mmu.h>
+#include <kernel/mutex.h>
+
+#include "pdcc.h"
+
+#define LOCAL_TRACE 0
+
+static struct pdcc_buffer_descriptor buffer_desc __ALIGNED(256);
+static paddr_t buffer_desc_phys;
+
+#define DCC_BUFLEN 256
+
+static uint8_t htod_buffer[DCC_BUFLEN] __ALIGNED(CACHE_LINE);
+static uint8_t dtoh_buffer[DCC_BUFLEN] __ALIGNED(CACHE_LINE);
+
+static uint htod_index;
+static uint htod_pos;
+static bool dtoh_filled;
+
+static void send_pdcc_command(uint32_t opcode, uint32_t data)
+{
+    uint32_t word;
+
+    word = PDCC_VALID |
+        ((opcode & 0x7f) << PDCC_OPCODE_SHIFT) |
+        (data & 0x00ffffff);
+
+    // XXX may block forever
+    LTRACEF("sending 0x%x\n", word);
+    arm_dcc_write(&word, 1, INFINITE_TIME);
+}
+
+static void send_buffer_header(void)
+{
+    send_pdcc_command(PDCC_OP_BUF_HEADER, buffer_desc_phys / 256);
+}
+
+static void send_reset(void)
+{
+    send_pdcc_command(PDCC_OP_RESET, 0);
+}
+
+static void send_out_index_update(uint32_t index)
+{
+    send_pdcc_command(PDCC_OP_UPDATE_OUT_INDEX, index);
+}
+
+static void send_buffer_consumed(void)
+{
+    send_pdcc_command(PDCC_OP_CONSUMED_IN, 0);
+}
+
+#define DCC_PROCESS_RESET 1
+static int dcc_process_opcode(uint32_t word)
+{
+    int ret = 0;
+
+    if (word & PDCC_VALID) {
+        uint32_t opcode = PDCC_OPCODE(word);
+        uint32_t data = PDCC_DATA(word);
+        LTRACEF("word 0x%x, opcode 0x%x, data 0x%x\n", word, opcode, data);
+        switch (opcode) {
+            case PDCC_OP_RESET:
+                htod_index = 0;
+                htod_pos = 0;
+                dtoh_filled = false;
+
+                // try to send the buffer header
+                send_buffer_header();
+                ret = DCC_PROCESS_RESET;
+                break;
+            case PDCC_OP_BUF_HEADER:
+                // we shouldn't get this
+                break;
+
+            case PDCC_OP_UPDATE_OUT_INDEX:
+                if (data > DCC_BUFLEN) {
+                    // out of range
+                    send_reset();
+                } else {
+                    htod_index = data;
+                    htod_pos = 0;
+                    arch_invalidate_cache_range((vaddr_t)htod_buffer, DCC_BUFLEN);
+                }
+                break;
+
+            case PDCC_OP_CONSUMED_IN:
+                arch_invalidate_cache_range((vaddr_t)dtoh_buffer, DCC_BUFLEN);
+                dtoh_filled = false;
+                break;
+            default:
+                TRACEF("bad opcode from host 0x%x\n", opcode);
+                send_reset();
+        }
+    }
+
+    return ret;
+}
+
+static ssize_t dcc_read(void *unused, void *_data, size_t len)
+{
+    unsigned char *data = _data;
+    size_t pos = 0;
+    uint32_t dcc;
+
+    LTRACEF("buf %p, len %zu, htod_pos %u, htod_index %u\n", _data, len, htod_pos, htod_index);
+
+    lk_time_t timeout = 0; // first dcc command should be with no timeout
+    while (pos < len) {
+        // process a dcc command
+        ssize_t err = arm_dcc_read(&dcc, 1, timeout);
+        if (err > 0) {
+            err = dcc_process_opcode(dcc);
+            if (err == DCC_PROCESS_RESET) {
+                return ERR_IO;
+            }
+        }
+
+        // see if there is any data in the incoming buffer
+        if (htod_index > 0) {
+            size_t tocopy = MIN(htod_index - htod_pos, len - pos);
+
+            memcpy(&data[pos], &htod_buffer[htod_pos], tocopy);
+            pos += tocopy;
+            htod_pos += tocopy;
+
+            // if we consumed everything, tell the host we're done with the buffer
+            if (htod_pos == htod_index) {
+                send_buffer_consumed();
+                htod_index = 0;
+                htod_pos = 0;
+                arch_invalidate_cache_range((vaddr_t)htod_buffer, DCC_BUFLEN);
+            }
+        }
+
+        timeout = 1000;
+    }
+
+    return 0;
+}
+
+static ssize_t dcc_write(void *unused, const void *_data, size_t len)
+{
+    const unsigned char *data = _data;
+    size_t pos = 0;
+
+    LTRACEF("buf %p, len %zu\n", _data, len);
+
+    while (pos < len) {
+        LTRACEF("pos %zu, len %zu, dtoh_filled %d\n", pos, len, dtoh_filled);
+        if (!dtoh_filled) {
+            // put as much data as we can in the outgoing buffer
+            size_t tocopy = MIN(len, DCC_BUFLEN);
+
+            LTRACEF("tocopy %zu\n", tocopy);
+            memcpy(dtoh_buffer, data, tocopy);
+            arch_clean_cache_range((vaddr_t)dtoh_buffer, DCC_BUFLEN);
+            send_out_index_update(tocopy);
+            dtoh_filled = true;
+
+            pos += tocopy;
+        }
+
+        // process a dcc command
+        uint32_t dcc;
+        ssize_t err = arm_dcc_read(&dcc, 1, 1000);
+        if (err > 0) {
+            err = dcc_process_opcode(dcc);
+            if (err == DCC_PROCESS_RESET) {
+                return ERR_IO;
+            }
+        }
+    }
+
+    return pos;
+}
+
+lkb_t *lkboot_check_dcc_open(void)
+{
+    lkb_t *lkb = NULL;
+
+    // read a dcc op and process it
+    {
+        uint32_t dcc;
+        ssize_t err = arm_dcc_read(&dcc, 1, 0);
+        if (err > 0) {
+            err = dcc_process_opcode(dcc);
+        }
+    }
+
+    if (htod_index > 0) {
+        // we have data, construct a lkb and return it
+        LTRACEF("we have data on dcc, starting command handler\n");
+        lkb = lkboot_create_lkb(NULL, dcc_read, dcc_write);
+    }
+
+    return lkb;
+}
+
+void lkboot_dcc_init(void)
+{
+    paddr_t pa;
+    __UNUSED status_t err;
+
+    buffer_desc.version = PDCC_VERSION;
+
+    err = arch_mmu_query((vaddr_t)htod_buffer, &pa, NULL);
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    buffer_desc.htod_buffer_phys = pa;
+    buffer_desc.htod_buffer_len = DCC_BUFLEN;
+
+    err = arch_mmu_query((vaddr_t)dtoh_buffer, &pa, NULL);
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    buffer_desc.dtoh_buffer_phys = pa;
+    buffer_desc.dtoh_buffer_len = DCC_BUFLEN;
+
+    err = arch_mmu_query((vaddr_t)&buffer_desc, &buffer_desc_phys, NULL);
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    arch_clean_cache_range((vaddr_t)&buffer_desc, sizeof(buffer_desc));
+}
+
diff --git a/src/bsp/lk/app/lkboot/include/app/lkboot.h b/src/bsp/lk/app/lkboot/include/app/lkboot.h
new file mode 100644
index 0000000..03f81a1
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/include/app/lkboot.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+typedef struct LKB lkb_t;
+
+// lkb_read/write may *only* be called from within a lkb_handler()
+// len of 0 is invalid for both
+
+// returns 0 on success, -1 on failure (io error, etc)
+int lkb_read(lkb_t *lkb, void *data, size_t len);
+int lkb_write(lkb_t *lkb, const void *data, size_t len);
+
+// len is the number of bytes the host has declared that it will send
+// use lkb_read() to read some or all of this data
+// return NULL on success, or an asciiz string (message) for error
+typedef const char* (*lkb_handler_t)(lkb_t *lkb,
+    const char *arg, unsigned len, void *cookie);
+
+// cmd must be a string constant
+void lkb_register(const char *cmd, lkb_handler_t handler, void *cookie);
+
diff --git a/src/bsp/lk/app/lkboot/inet.c b/src/bsp/lk/app/lkboot/inet.c
new file mode 100644
index 0000000..defac38
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/inet.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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.
+ */
+
+#if WITH_LIB_MINIP
+#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 <app/lkboot.h>
+
+#include "lkboot.h"
+
+#include <lib/minip.h>
+
+#define LOCAL_TRACE 0
+
+static ssize_t tcp_readx(void *s, void *_data, size_t len) {
+    char *data = _data;
+    while (len > 0) {
+        int r = tcp_read(s, data, len);
+        if (r <= 0) return -1;
+        data += r;
+        len -= r;
+    }
+    return 0;
+}
+
+lkb_t *lkboot_tcp_opened(void *s)
+{
+    lkb_t *lkb;
+
+    lkb = lkboot_create_lkb(s, tcp_readx, (void *)tcp_write);
+    if (!lkb)
+        return NULL;
+
+    return lkb;
+}
+
+#endif
diff --git a/src/bsp/lk/app/lkboot/lkboot.c b/src/bsp/lk/app/lkboot/lkboot.c
new file mode 100644
index 0000000..a5b432e
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/lkboot.c
@@ -0,0 +1,354 @@
+/*
+ * 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
diff --git a/src/bsp/lk/app/lkboot/lkboot.h b/src/bsp/lk/app/lkboot/lkboot.h
new file mode 100644
index 0000000..4d3bb1c
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/lkboot.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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.
+ */
+#pragma once
+
+#include <sys/types.h>
+#include <app/lkboot.h>
+#include "lkboot_protocol.h"
+
+/* private to lkboot app */
+
+int lkb_handle_command(lkb_t *lkb, const char *cmd, const char *arg, size_t len, const char **result);
+
+status_t do_flash_boot(void);
+
+typedef ssize_t lkb_read_hook(void *s, void *data, size_t len);
+typedef ssize_t lkb_write_hook(void *s, const void *data, size_t len);
+
+lkb_t *lkboot_create_lkb(void *cookie, lkb_read_hook *read, lkb_write_hook *write);
+status_t lkboot_process_command(lkb_t *);
+
+/* inet server */
+lkb_t *lkboot_tcp_opened(void *s);
+
+/* dcc based server */
+void lkboot_dcc_init(void);
+lkb_t *lkboot_check_dcc_open(void);
+
diff --git a/src/bsp/lk/app/lkboot/lkboot_protocol.h b/src/bsp/lk/app/lkboot/lkboot_protocol.h
new file mode 100644
index 0000000..bc34b09
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/lkboot_protocol.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+#pragma once
+
+typedef struct {
+    unsigned char opcode;
+    unsigned char extra;
+    unsigned short length;
+} msg_hdr_t;
+
+// unless otherwise specified, extra is always zero.
+
+#define MSG_OKAY    0x00
+// length must be zero.
+// server indicates command was successful.
+
+#define MSG_FAIL    0xFF
+// length may be nonzero, if so data is a human readable error message
+// extra may be nonzero, if so it is a more specific error code
+
+#define MSG_LOG     0xFE
+// data contains human readable log message from server
+// server may issue these at any time
+
+#define MSG_GO_AHEAD    0x01
+// length must be zero
+// server indicates that command was valid and it is ready for data
+// client should send MSG_SEND_DATA messages to transfer data
+
+#define MSG_CMD     0x40
+// length must be greater than zero
+// data will contain an ascii command
+// server may reject excessively large commands
+
+#define MSG_SEND_DATA   0x41
+// client sends data to server
+// length is datalen -1 (to allow for full 64k chunks)
+
+#define MSG_END_DATA    0x42
+// client ends data stream
+// server will then respond with MSG_OKAY or MSG_FAIL
+
+// command strings are in the form of
+// <command> ':' <decimal-datalen> ':' <optional-arguments>
+
+// example:
+// C: MSG_CMD "flash:32768:bootloader"
+// S: MSG_GO_AHEAD
+// C: MSG_SEND_DATA 16384 ...
+// C: MSG_SEND_DATA 16384 ...
+// C: MSG_END_DATA
+// S: MSG_LOG "erasing sectors"
+// S: MSG_LOG "writing sectors"
+// S: MSG_OKAY
+//
+// C: MSG_CMD "eraese:0:bootloader"
+// S: MSG_FAIL "unknown command 'eraese'"
+//
+// C: MSG_CMD "reboot:0:"
+// S: MSG_OKAY
diff --git a/src/bsp/lk/app/lkboot/pdcc.h b/src/bsp/lk/app/lkboot/pdcc.h
new file mode 100644
index 0000000..068b878
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/pdcc.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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.
+ */
+#pragma once
+
+#include <stdint.h>
+
+/* in memory and DCC descriptors for the PDCC protocol */
+
+/* shared outside of lk repository, be careful of modifications */
+#define PDCC_VERSION 1
+
+struct pdcc_buffer_descriptor {
+    uint32_t version;
+
+    uint32_t htod_buffer_phys;
+    uint32_t htod_buffer_len;
+
+    uint32_t dtoh_buffer_phys;
+    uint32_t dtoh_buffer_len;
+};
+
+#define PDCC_VALID (1<<31)
+#define PDCC_OPCODE_SHIFT (24)
+#define PDCC_OPCODE(x) (((x) >> PDCC_OPCODE_SHIFT) & 0x7f)
+#define PDCC_DATA(x) ((x) & 0x00ffffff);
+
+enum {
+    PDCC_OP_RESET = 0,
+    PDCC_OP_BUF_HEADER,
+    PDCC_OP_UPDATE_OUT_INDEX,
+    PDCC_OP_CONSUMED_IN,
+};
+
diff --git a/src/bsp/lk/app/lkboot/rules.mk b/src/bsp/lk/app/lkboot/rules.mk
new file mode 100644
index 0000000..1ee1376
--- /dev/null
+++ b/src/bsp/lk/app/lkboot/rules.mk
@@ -0,0 +1,19 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_DEPS += \
+	lib/bio \
+	lib/bootargs \
+	lib/bootimage \
+	lib/cbuf \
+	lib/ptable \
+	lib/sysparam
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/commands.c \
+	$(LOCAL_DIR)/dcc.c \
+	$(LOCAL_DIR)/inet.c \
+	$(LOCAL_DIR)/lkboot.c \
+
+include make/module.mk